“typedef”和“使用”在C++11中有什么区别?

我知道在C++11中,我们现在可以使用using来编写类型别名,例如typedefs:

typedef int MyInt;

据我所知,相当于:

using MyInt = int;

这种新语法来自于努力找到一种表达“模板typedef”的方法:

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

但是,对于前两个非模板示例,标准中还有其他细微的差异吗?例如,typedef确实存在“弱”混淆现象。也就是说,它不创建新类型,而只创建新名称(这些名称之间的转换是隐含的)。

它和using是一样的还是生成了一个新类型?有什么区别吗?

386261 次浏览

它们是等价的,从标准(强调我的)(7.1.3.2):

typedef-name也可以通过别名声明引入。这个使用关键字后的标识符变为typedef-name,并且标识符后面的可选属性-说明符-seq到那个typedef-name。它具有相同的语义学,就好像它是由typedef说明符引入。特别是,它不定义新类型,它不会出现在type-id中。

在模板中使用使用语法有一个优点。如果你需要类型抽象,但也需要保留模板参数以便将来可能指定。你应该这样写。

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; }

但是使用语法简化了这个用例。

template <typename T> using my_type = whatever<T>;
my_type<int> variable;template <typename U> struct baz { my_type<U> _var_member; }

它们基本相同,除了:

别名声明与模板兼容,而C样式typedef不是。

它们本质上是相同的,但using提供了alias templates,这非常有用。我可以找到一个很好的例子如下:

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

所以,我们可以使用std::add_const_t<T>而不是typename std::add_const<T>::type

我知道最初的海报有一个很好的答案,但是对于任何像我这样在这个线程上跌跌撞撞的人来说,我认为该提案中有一个重要的注释,我认为它为这里的讨论增加了一些价值,特别是在评论中关于typedef关键字是否会在未来被标记为弃用的担忧,或者因为冗余/旧而被删除:

有人建议(重新)使用关键字typedef…来引入模板别名:

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

这种表示法的优点是使用已知的关键字来引入类型别名。然而,它也显示了几个缺点,其中包括在别名不指定类型而是模板的上下文中使用已知关键字来引入类型名称的别名的混乱;Vec没有类型的别名,不应被视为typedef-name。名称Vec是家族std::vector<•, MyAllocator<•> >的名称-其中项目符号是类型名称的占位符。因此,我们不建议“typedef”语法。另一方面,句子

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

可以读作/解释为:从现在开始,我将使用#0作为#1的同义词。通过这种阅读,混淆现象的新语法似乎是合理的。

对我来说,这意味着在C++中继续支持typedef关键字,因为它仍然可以使代码更可读和可理解

更新using关键字是专门针对模板的,并且(正如在接受的答案中指出的那样)当您使用非模板时,usingtypedef在机械上是相同的,因此选择完全取决于程序员的易读性和意图沟通。

这两个关键字是等价的,但有一些注意事项。一个是使用using T = int (*)(int, int);声明函数指针比使用typedef int (*T)(int, int);更清晰。第二个是typedef不可能使用模板别名形式。第三个是公开C API需要在公共标头中使用typedef

以下所有标准引用均指N4659:2017年3月科纳工作草案/C++17 DIS


Typedef声明可以用作初始化语句,而alias声明不能(+)

但是,对于前两个非模板示例,还有其他细微差别吗?

  • 差异在语义学:无。
  • 差异在允许的情况下:一些(++)

(+)P2360R0扩展init语句以允许别名声明已获CWG批准和C++23,typedef声明和别名声明之间的不一致将被删除。
(++)除了别名模板的例子,这在原来的帖子中已经提到了。

同样的语义学

[dcl.typedef]/2[提取,强调矿]管辖

[dcl.typedef]/2Atypedef-name也可以由一个别名-声明using关键字后面的标识符变成了typedef参数名标识符后面的可选属性-说明符-seq属于该typedef参数名这样的typedef-nametypedef说明符引入的语义学相同。

别名声明引入的typedef参数名具有同样的语义学,就好像它是由typedef声明引入的一样。

允许上下文中的细微差异

然而,这并不意味着这两个变体对的背景有相同的限制,它们可以在其中使用。事实上,尽管是一个角落情况,typedef声明在其中-声明,因此可以在允许初始化语句的上下文中使用

// C++11 (C++03) (init. statement in for loop iteration statements).for (typedef int Foo; Foo{} != 0;)//   ^^^^^^^^^^^^^^^ init-statement{}
// C++17 (if and switch initialization statements).if (typedef int Foo; true)//  ^^^^^^^^^^^^^^^ init-statement{(void)Foo{};}
switch (typedef int Foo; 0)//      ^^^^^^^^^^^^^^^ init-statement{case 0: (void)Foo{};}
// C++20 (range-based for loop initialization statements).std::vector<int> v{1, 2, 3};for (typedef int Foo; Foo f : v)//   ^^^^^^^^^^^^^^^ init-statement{(void)f;}
for (typedef struct { int x; int y;} P; auto [x, y] : {P{1, 1}, {1, 2}, {3, 5}})//   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ init-statement{(void)x;(void)y;}

别名-声明没有初始化语句,因此可能不会用于允许初始化语句的上下文

// 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

到目前为止,C++23将使typedefusing更接近:P2360建议using构成初始化语句,例如@李飞飞的答案中列出的error: expected expression

但是,即使使用P2360,typedef也不能成为模板。

编辑[2022-09-14]:这包含错误的信息。)

总的来说,usingtypedef更强大,IMO也更具可读性。