Typedefs nội bộ trong C ++ - phong cách tốt hay phong cách xấu?


179

Một cái gì đó tôi đã thấy mình làm thường xuyên gần đây là tuyên bố typedefs có liên quan đến một lớp cụ thể trong lớp đó, tức là

class Lorem
{
    typedef boost::shared_ptr<Lorem> ptr;
    typedef std::vector<Lorem::ptr>  vector;

//
// ...
//
};

Các loại này sau đó được sử dụng ở nơi khác trong mã:

Lorem::vector lorems;
Lorem::ptr    lorem( new Lorem() );

lorems.push_back( lorem );

Những lý do tôi thích nó:

  • Nó làm giảm tiếng ồn được giới thiệu bởi các mẫu lớp, std::vector<Lorem>trở thành Lorem::vector, v.v.
  • Nó phục vụ như một tuyên bố về ý định - trong ví dụ trên, lớp Lorem được dự định là tham chiếu được tính thông qua boost::shared_ptrvà được lưu trữ trong một vectơ.
  • Nó cho phép thực hiện thay đổi - tức là nếu Lorem cần được thay đổi để được tham chiếu xâm nhập được tính (thông qua boost::intrusive_ptr) ở giai đoạn sau thì điều này sẽ có tác động tối thiểu đến mã.
  • Tôi nghĩ rằng nó trông 'đẹp hơn' và dễ đọc hơn.

Những lý do tôi không thích nó:

  • Đôi khi có một số vấn đề với các phụ thuộc - nếu bạn muốn nhúng, giả sử, Lorem::vectortrong một lớp khác nhưng chỉ cần (hoặc muốn) chuyển tiếp khai báo Lorem (trái ngược với việc giới thiệu một phụ thuộc vào tệp tiêu đề của nó) thì cuối cùng bạn phải sử dụng loại rõ ràng (ví dụ boost::shared_ptr<Lorem>chứ không phải Lorem::ptr), đó là một chút không nhất quán.
  • Nó có thể không phổ biến lắm, và do đó khó hiểu hơn?

Tôi cố gắng khách quan với phong cách mã hóa của mình, vì vậy sẽ rất tốt nếu có một số ý kiến ​​khác về nó để tôi có thể mổ xẻ suy nghĩ của mình một chút.

Câu trả lời:


153

Tôi nghĩ đó là phong cách tuyệt vời, và tôi sử dụng nó cho mình. Luôn luôn tốt nhất là giới hạn phạm vi tên càng nhiều càng tốt và sử dụng các lớp là cách tốt nhất để làm điều này trong C ++. Ví dụ, thư viện C ++ Standard sử dụng nhiều typedefs trong các lớp.


Đó là một điểm tốt, tôi tự hỏi nó trông 'đẹp hơn' là tiềm thức của tôi tinh tế chỉ ra rằng phạm vi hạn chế là một điều tốt . Mặc dù vậy, tôi tự hỏi, thực tế là STL sử dụng nó chủ yếu trong các mẫu lớp làm cho nó sử dụng một cách tinh tế? Có khó hơn để biện minh trong một lớp 'cụ thể' không?
Will Baker

1
Vâng, thư viện tiêu chuẩn được tạo thành từ các mẫu chứ không phải các lớp, nhưng tôi nghĩ rằng sự biện minh là giống nhau cho cả hai.

14

Nó phục vụ như một tuyên bố về ý định - trong ví dụ trên, lớp Lorem được dự định là tham chiếu được tính thông qua boost :: shared_ptr và được lưu trữ trong một vectơ.

Đây chính xác là những gì nó không làm.

Nếu tôi thấy 'Foo :: Ptr' trong mã, tôi hoàn toàn không biết đó là shared_ptr hay Foo * (STL có :: con trỏ typedefs là T *, hãy nhớ) hoặc bất cứ điều gì. Đặc biệt nếu đó là một con trỏ dùng chung, tôi hoàn toàn không cung cấp một typedef, nhưng hãy sử dụng shared_ptr một cách rõ ràng trong mã.

Trên thực tế, tôi hầu như không bao giờ sử dụng typedefs bên ngoài Mẫu Metaprogramming.

STL thực hiện loại điều này mọi lúc

Thiết kế STL với các khái niệm được định nghĩa theo các hàm thành viên và typedefs lồng nhau là một thư viện mẫu hiện đại, các thư viện mẫu hiện đại sử dụng các hàm và đặc điểm miễn phí (xem Boost.Graph), vì các loại này không loại trừ các loại tích hợp từ mô hình hóa khái niệm và bởi vì nó làm cho các kiểu thích ứng không được thiết kế với các khái niệm của các thư viện mẫu đã cho dễ dàng hơn.

Đừng sử dụng STL như một lý do để mắc lỗi tương tự.


Tôi đồng ý với phần đầu tiên của bạn, nhưng chỉnh sửa gần đây của bạn là một chút thiển cận. Các kiểu lồng nhau như vậy đơn giản hóa định nghĩa của các lớp tính trạng, vì chúng cung cấp một mặc định hợp lý. Hãy xem xét std::allocator_traits<Alloc>lớp mới ... bạn không phải chuyên môn hóa nó cho mỗi cấp phát duy nhất bạn viết, bởi vì nó chỉ đơn giản là mượn các loại trực tiếp từ đó Alloc.
Dennis Zickefoose

@Dennis: Trong C ++, sự tiện lợi phải ở bên cạnh / người dùng / của thư viện, chứ không phải ở phía / tác giả /: người dùng muốn có giao diện thống nhất cho một đặc điểm và chỉ một lớp đặc điểm mới có thể cung cấp cho điều đó, vì những lý do nêu trên). Nhưng ngay cả với tư cách là một Alloctác giả, việc chuyên môn hóa std::allocator_traits<>cho loại hình mới của anh ta không khó hơn nhiều so với việc thêm các typedefs cần thiết. Tôi cũng đã chỉnh sửa câu trả lời, vì câu trả lời đầy đủ của tôi không phù hợp với nhận xét.
Marc Mutz - mmutz

Nhưng nó ở phía bên của người dùng. Là một người sử dụng của allocator_traitscố gắng để tạo ra một cấp phát tùy chỉnh, tôi không cần phải bận tâm với mười lăm thành viên của lớp đặc điểm ... tất cả tôi phải làm là nói typedef Blah value_type;và cung cấp các chức năng thành viên thích hợp, và mặc định allocator_traitssẽ tìm ra nghỉ ngơi. Hơn nữa, hãy xem ví dụ của bạn về Boost.Graph. Đúng, nó sử dụng rất nhiều lớp đặc điểm ... nhưng việc thực hiện mặc định các graph_traits<G>truy vấn đơn giản Gcho các typedefs nội bộ của chính nó.
Dennis Zickefoose

1
Và thậm chí 03 thư viện tiêu chuẩn sử dụng các lớp đặc điểm khi thích hợp ... triết lý của thư viện không phải là hoạt động trên các container một cách tổng quát, mà là vận hành trên các trình vòng lặp. Vì vậy, nó cung cấp một iterator_traitslớp để các thuật toán chung của bạn có thể dễ dàng truy vấn thông tin phù hợp. Mà, một lần nữa, mặc định để truy vấn iterator cho thông tin riêng của nó. Điểm dài và ngắn của nó là các đặc điểm và typedefs nội bộ hầu như không loại trừ lẫn nhau ... chúng hỗ trợ lẫn nhau.
Dennis Zickefoose

1
@Dennis: iterator_traitstrở nên cần thiết vì T*phải là một mô hình của RandomAccessIterator, nhưng bạn không thể đặt các typedefs cần thiết vào T*. Khi chúng tôi đã có iterator_traits, các typedefs lồng nhau trở nên dư thừa, và tôi ước chúng đã được gỡ bỏ ở đó và sau đó. Vì lý do tương tự (không thể thêm typedefs nội bộ), T[N]không mô hình hóa Sequencekhái niệm STL và bạn cần các loại bùn như std::array<T,N>. Boost.Range cho thấy làm thế nào một khái niệm Sequence hiện đại có thể được định nghĩa T[N]có thể mô hình hóa, bởi vì nó không yêu cầu typedefs lồng nhau, cũng không phải các hàm thành viên.
Marc Mutz - mmutz


8

Typdefs chắc chắn là phong cách tốt. Và tất cả "lý do tôi thích" của bạn là tốt và chính xác.

Về những vấn đề bạn có với điều đó. Vâng, tuyên bố phía trước không phải là một chén thánh. Bạn chỉ có thể thiết kế mã của mình để tránh phụ thuộc nhiều cấp.

Bạn có thể di chuyển typedef bên ngoài lớp nhưng Class :: ptr đẹp hơn ClassPtr rất nhiều mà tôi không làm điều này. Nó giống như với các không gian tên như đối với tôi - mọi thứ được kết nối trong phạm vi.

Đôi khi tôi đã làm

Trait<Loren>::ptr
Trait<Loren>::collection
Trait<Loren>::map

Và nó có thể được mặc định cho tất cả các lớp miền và với một số chuyên môn cho một số lớp nhất định.


3

STL thực hiện loại điều này mọi lúc - các typedefs là một phần của giao diện cho nhiều lớp trong STL.

reference
iterator
size_type
value_type
etc...

là tất cả các typedefs là một phần của giao diện cho các lớp mẫu STL khác nhau.


Đúng, và tôi nghi ngờ đây là nơi đầu tiên tôi nhặt nó lên. Có vẻ như những điều này sẽ dễ dàng hơn một chút để biện minh mặc dù? Tôi không thể giúp xem các typedefs trong một mẫu lớp gần giống với các biến hơn, nếu bạn tình cờ nghĩ theo dòng 'lập trình meta'.
Will Baker

3

Một phiếu khác cho điều này là một ý tưởng tốt. Tôi bắt đầu làm điều này khi viết một mô phỏng phải hiệu quả, cả về thời gian và không gian. Tất cả các loại giá trị có một typedef Ptr bắt đầu như một con trỏ chia sẻ tăng. Sau đó tôi đã thực hiện một số hồ sơ và thay đổi một số trong số chúng thành một con trỏ xâm nhập mà không phải thay đổi bất kỳ mã nào trong đó các đối tượng được sử dụng.

Lưu ý rằng điều này chỉ hoạt động khi bạn biết nơi các lớp sẽ được sử dụng và tất cả các sử dụng đều có cùng một yêu cầu. Tôi sẽ không sử dụng điều này trong mã thư viện, ví dụ, bởi vì bạn không thể biết khi viết thư viện bối cảnh sẽ sử dụng nó.


3

Hiện tại tôi đang làm việc về mã, sử dụng mạnh mẽ các loại typedefs này. Cho đến nay là tốt.

Nhưng tôi nhận thấy rằng có khá nhiều typedefs lặp đi lặp lại, các định nghĩa được phân chia giữa một số lớp và bạn không bao giờ thực sự biết bạn đang đối phó với loại nào. Nhiệm vụ của tôi là tóm tắt kích thước của một số cấu trúc dữ liệu phức tạp ẩn đằng sau các typedefs này - vì vậy tôi không thể dựa vào các giao diện hiện có. Kết hợp với ba đến sáu cấp độ không gian tên lồng nhau và sau đó nó trở nên khó hiểu.

Vì vậy, trước khi sử dụng chúng, có một số điểm cần được xem xét

  • Có ai khác cần những typedefs? Là lớp học được sử dụng rất nhiều bởi các lớp khác?
  • Tôi có rút ngắn thời gian sử dụng hoặc ẩn lớp không? (Trong trường hợp ẩn bạn cũng có thể nghĩ về giao diện.)
  • Là những người khác làm việc với mã? Họ làm nó như thế nào? Họ sẽ nghĩ rằng nó dễ dàng hơn hay họ sẽ trở nên bối rối?

1

Tôi khuyên bạn nên di chuyển những typedefs bên ngoài lớp học. Bằng cách này, bạn loại bỏ sự phụ thuộc trực tiếp vào các lớp con trỏ và vectơ được chia sẻ và bạn chỉ có thể bao gồm chúng khi cần. Trừ khi bạn đang sử dụng các loại đó trong triển khai lớp học của mình, tôi nghĩ rằng chúng không nên là typedefs bên trong.

Những lý do bạn thích nó vẫn được khớp, vì chúng được giải quyết bằng cách đặt bí danh thông qua typedef, không phải bằng cách khai báo chúng trong lớp của bạn.


Điều đó sẽ đánh bại không gian tên nặc danh với typedefs phải không?! Vấn đề với typedef là nó ẩn loại thực tế, có thể gây ra xung đột khi được bao gồm trong / bởi nhiều mô-đun, rất khó tìm / sửa. Đó là một thực hành tốt để chứa chúng trong không gian tên hoặc bên trong các lớp.
Indy9000

3
Xung đột tên và cảnh báo không gian tên ẩn danh ít có liên quan đến việc giữ một kiểu chữ bên trong một lớp hoặc bên ngoài. Bạn có thể có một cuộc xung đột tên với lớp của bạn, không phải với typedefs của bạn. Vì vậy, để tránh cảnh báo tên, sử dụng không gian tên. Khai báo lớp của bạn và các typedefs liên quan trong một không gian tên.
Cătălin Pitiș

Một đối số khác để đặt typedef bên trong một lớp là việc sử dụng các hàm templatised. Khi, giả sử, một hàm nhận một loại vùng chứa không xác định (vectơ hoặc danh sách) có chứa một loại chuỗi không xác định (chuỗi hoặc biến thể tuân thủ chuỗi của riêng bạn). cách duy nhất để tìm ra loại tải trọng container là với typedef 'value_type' là một phần của định nghĩa lớp container.
Marius

1

Khi typedef chỉ được sử dụng trong chính lớp đó (nghĩa là được khai báo là riêng tư) tôi nghĩ đó là một ý tưởng tốt. Tuy nhiên, vì chính xác những lý do bạn đưa ra, tôi sẽ không sử dụng nó nếu nhu cầu của typedef cần được biết đến bên ngoài lớp học. Trong trường hợp đó tôi khuyên bạn nên di chuyển chúng ra ngoài lớp.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.