Sự khác biệt giữa 'typedef' và 'using' trong C ++ 11 là gì?


905

Tôi biết rằng trong C ++ 11 bây giờ chúng ta có thể sử dụng usingđể viết bí danh, như typedefs:

typedef int MyInt;

Là, từ những gì tôi hiểu, tương đương với:

using MyInt = int;

Và cú pháp mới đó xuất hiện từ nỗ lực để có cách diễn đạt " template typedef":

template< class T > using MyType = AnotherType< T, MyAllocatorType >;

Nhưng, với hai ví dụ phi mẫu đầu tiên, có sự khác biệt tinh tế nào khác trong tiêu chuẩn không? Ví dụ: typedefs làm răng cưa theo cách "yếu". Đó là nó không tạo ra một loại mới mà chỉ có một tên mới (chuyển đổi được ẩn giữa các tên đó).

Nó giống với usinghoặc nó tạo ra một loại mới? Có sự khác biệt nào không?


215
Cá nhân tôi thích cú pháp mới vì nó giống với phép gán biến thông thường hơn, cải thiện khả năng đọc. Ví dụ, bạn thích typedef void (&MyFunc)(int,int);hay using MyFunc = void(int,int);?
Matthieu M.

13
Tôi hoàn toàn đồng ý, tôi chỉ sử dụng cú pháp mới bây giờ. Đó là lý do tại sao tôi đã hỏi, để chắc chắn rằng thực sự không có sự khác biệt.
Klaim

80
@MatthieuM. hai cái đó là btw khác nhau. Nó phải là typedef void MyFunc(int,int);(mà thực sự trông không tệ lắm), hoặcusing MyFunc = void(&)(int,int);
R. Martinho Fernandes

5
@ R.MartinhoFernandes tại sao bạn cần (&) trong using MyFunc = void(&)(int,int);? nó có nghĩa MyFunclà một tham chiếu đến một chức năng? Điều gì nếu bạn bỏ qua & ?
Giàu

7
Vâng, đó là một tài liệu tham khảo chức năng. Nó tương đương với typedef void (&MyFunc)(int,int);. Nếu bạn bỏ qua &nó tương đương vớitypedef void MyFunc(int,int);
R. Martinho Fernandes

Câu trả lời:


585

Chúng là tương đương, từ tiêu chuẩn (nhấn mạnh của tôi) (7.1.3.2):

Tên typedef cũng có thể được giới thiệu bằng một khai báo bí danh. Mã định danh theo sau từ khóa đang sử dụng trở thành tên typedef và thuộc tính-specifier-seq tùy chọn theo sau định danh xác định với tên typedef đó. Nó có cùng ngữ nghĩa như thể được giới thiệu bởi công cụ xác định typedef. Cụ thể, nó không định nghĩa một loại mới và nó sẽ không xuất hiện trong id loại.


24
Từ câu trả lời, usingtừ khóa dường như là một siêu từ typedef. Sau đó, sẽ typdefbị phản đối trong tương lai?
iammilind

49
Khấu hao không nhất thiết chỉ ra ý định loại bỏ - Nó chỉ đơn thuần là một khuyến nghị rất mạnh mẽ để thích một phương tiện khác.
Cứu tinh sắt

18
Nhưng sau đó tôi tự hỏi tại sao họ không cho phép typedef được tạo mẫu. Tôi nhớ đã đọc ở đâu đó rằng họ đã giới thiệu usingcú pháp chính xác bởi vì typedefngữ nghĩa không hoạt động tốt với các mẫu. Mà bằng cách nào đó mâu thuẫn với thực tế usingđược xác định là có chính xác cùng một ngữ nghĩa.
celtschk

12
@celtschk: Lý do được nói đến trong đề xuất n1489. Bí danh mẫu không phải là bí danh cho một loại, mà là bí danh cho một nhóm các mẫu. Để phân biệt giữa typedefcảm thấy cần một cú pháp mới. Ngoài ra, hãy nhớ rằng câu hỏi của OP là về sự khác biệt giữa các phiên bản không phải mẫu.
Jesse Tốt

4
Vậy tại sao sự dư thừa này được giới thiệu? 2 cú pháp cho cùng một mục đích. Và tôi không thấy typdefbị phản đối bao giờ.
Benoît

237

Chúng phần lớn giống nhau, ngoại trừ:

Khai báo bí danh tương thích với các mẫu, trong khi typedef kiểu C thì không.


29
Đặc biệt thích sự đơn giản của câu trả lời và chỉ ra nguồn gốc của sắp chữ.
g24l

1
@ g24l ý bạn là typedef ... có lẽ
Marc.2377

Sự khác biệt giữa C và C ++ là typedefgì nếu tôi có thể hỏi?
McSinyx

196

Các sử dụng cú pháp có lợi thế khi được sử dụng trong các mẫu. Nếu bạn cần loại trừu tượng, nhưng cũng cần giữ tham số mẫu để có thể được chỉ định trong tương lai. Bạn nên viết một cái gì đó như thế này.

template <typename T> struct whatever {};

template <typename T> struct rebind
{
  typedef whatever<T> type; // to make it possible to substitue the whatever in future.
};

rebind<int>::type variable;

template <typename U> struct bar { typename rebind<U>::type _var_member; }

Nhưng sử dụng cú pháp đơn giản hóa trường hợp sử dụng này.

template <typename T> using my_type = whatever<T>;

my_type<int> variable;
template <typename U> struct baz { my_type<U> _var_member; }

35
Tôi đã chỉ ra điều này trong câu hỏi mặc dù. Câu hỏi của tôi là nếu bạn không sử dụng mẫu thì có sự khác biệt nào với typedef không. Như, ví dụ, khi bạn sử dụng 'Foo foo {init_value};' thay vì 'Foo foo (init_value)' cả hai đều phải làm điều tương tự nhưng không thực hiện chính xác các quy tắc giống nhau. Vì vậy, tôi đã tự hỏi nếu có một sự khác biệt ẩn tương tự với việc sử dụng / typedef.
Klaim

24

Chúng cơ bản giống nhau nhưng usingcung cấp alias templateskhá hữu ích. Một ví dụ điển hình tôi có thể tìm thấy như sau:

namespace std {
 template<typename T> using add_const_t = typename add_const<T>::type;
}

Vì vậy, chúng ta có thể sử dụng std::add_const_t<T>thay vìtypename std::add_const<T>::type


Theo như tôi biết, đó là hành vi không xác định để thêm bất cứ thứ gì vào trong không gian tên std
someonewithpc

5
@someonewithpc Tôi không thêm bất cứ thứ gì, nó đã tồn tại, tôi chỉ hiển thị một ví dụ về việc sử dụng tên chữ. Vui lòng kiểm tra en.cppreference.com/w/cpp/types/add_cv
Oculus

9

Tôi biết người đăng ban đầu có một câu trả lời tuyệt vời, nhưng đối với bất kỳ ai vấp phải chủ đề này như tôi có một lưu ý quan trọng từ đề xuất mà tôi nghĩ sẽ bổ sung thêm giá trị cho cuộc thảo luận ở đây, đặc biệt là những lo ngại trong các nhận xét về việc nếu typedeftừ khóa là sẽ bị đánh dấu là không dùng nữa trong tương lai hoặc bị xóa vì dư thừa / cũ:

Nó đã được đề xuất (tái) sử dụng từ khóa typedef ... để giới thiệu bí danh mẫu:

template<class T>
  typedef std::vector<T, MyAllocator<T> > Vec;

Ký hiệu đó có lợi thế là sử dụng một từ khóa đã biết để giới thiệu một bí danh loại. Tuy nhiên, nó cũng hiển thị một số biến dạng [sic] trong đó có sự nhầm lẫn khi sử dụng từ khóa được biết để giới thiệu bí danh cho một tên loại trong ngữ cảnh mà bí danh không chỉ định một loại, mà là một mẫu; Veckhông một bí danh cho một loại, và không nên được thực hiện cho một typedef-name. Tên Vecnày là tên của gia đình std::vector<•, MyAllocator<•> >- trong đó viên đạn là một trình giữ chỗ cho một loại tên. Thông thường, chúng tôi không đề xuất cú pháp typedef ấn. Mặt khác, câu

template<class T>
  using Vec = std::vector<T, MyAllocator<T> >;

có thể được đọc / giải thích như sau: từ bây giờ, tôi sẽ sử dụng Vec<T>làm từ đồng nghĩa chostd::vector<T, MyAllocator<T> > . Với cách đọc đó, cú pháp mới cho bí danh có vẻ hợp lý hợp lý.

Đối với tôi, điều này ngụ ý tiếp tục hỗ trợ cho typedeftừ khóa trong C ++ vì nó vẫn có thể làm cho mã dễ đọc và dễ hiểu hơn .

Cập nhật using từ khóa đã được cụ thể đối với các mẫu, và (như đã được chỉ ra trong câu trả lời được chấp nhận) khi bạn đang làm việc với những người không mẫu usingtypedeflà một cách máy móc giống hệt nhau, vì vậy sự lựa chọn là hoàn toàn lên đến các lập trình viên trên cơ sở khả năng đọc và truyền thông về ý định .


2

Cả hai từ khóa đều tương đương, nhưng có một vài cảnh báo. Một là khai báo một con trỏ hàm với using T = int (*)(int, int);rõ ràng hơn với typedef int (*T)(int, int);. Thứ hai là mẫu bí danh mẫu là không thể với typedef. Thứ ba là việc phơi bày API C sẽ yêu cầu typedeftrong các tiêu đề công khai.


0

Khai báo Typedef có thể, trong khi khai báo bí danh không thể, được sử dụng làm câu lệnh khởi tạo

Nhưng, với hai ví dụ phi mẫu đầu tiên, có sự khác biệt tinh tế nào khác trong tiêu chuẩn không?

Mặc dù là một trường hợp góc, một khai báo typedef là một câu lệnh init và do đó có thể được sử dụng trong các ngữ cảnh cho phép các câu lệnh khởi tạo

// C++11 (C++03) (init. statement in for loop iteration statements).
for(typedef int Foo; Foo{} != 0;) {}

// C++17 (if and switch initialization statements).
if (typedef int Foo; true) { (void)Foo{}; }
//  ^^^^^^^^^^^^^^^ init-statement

switch(typedef int Foo; 0) { case 0: (void)Foo{}; }
//     ^^^^^^^^^^^^^^^ init-statement

// C++20 (range-based for loop initialization statements).
std::vector<int> v{1, 2, 3};
for(typedef int Foo; Foo f : v) { (void)f; }
//  ^^^^^^^^^^^^^^^ init-statement

trong khi một bí danh-khaikhông một init-tuyên bố , và có thể do đó không được sử dụng trong bối cảnh cho phép báo cáo khởi

// C++ 11.
for(using Foo = int; Foo{} != 0;) {}
//  ^^^^^^^^^^^^^^^ error: expected expression

// C++17 (initialization expressions in switch and if statements).
if (using Foo = int; true) { (void)Foo{}; }
//  ^^^^^^^^^^^^^^^ error: expected expression

switch(using Foo = int; 0) { case 0: (void)Foo{}; }
//     ^^^^^^^^^^^^^^^ error: expected expression

// C++20 (range-based for loop initialization statements).
std::vector<int> v{1, 2, 3};
for(using Foo = int; Foo f : v) { (void)f; }
//  ^^^^^^^^^^^^^^^ error: expected expression
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.