【问题标题】:What is the difference between 'typedef' and 'using' in C++11?C ++ 11中的'typedef'和'using'有什么区别?
【发布时间】:2012-05-31 15:30:39
【问题描述】:

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

typedef int MyInt;

据我了解,相当于:

using MyInt = int;

新语法源于努力表达“模板类型定义”:

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

但是,对于前两个非模板示例,标准中是否还有其他细微差别?例如,typedefs 以“弱”的方式进行别名。也就是说,它不会创建新类型,而只会创建新名称(这些名称之间隐含转换)。

和using一样还是生成新类型?有什么不同吗?

【问题讨论】:

  • 我个人更喜欢新语法,因为它更类似于常规变量赋值,提高了可读性。例如,您更喜欢typedef void (&amp;MyFunc)(int,int); 还是using MyFunc = void(int,int);?
  • 我完全同意,我现在只使用新语法。这就是我问的原因,以确保确实没有区别。
  • @MatthieuM。顺便说一句,这两个是不同的。应该是typedef void MyFunc(int,int);(实际上看起来还不错),或者using MyFunc = void(&amp;)(int,int);
  • @R.MartinhoFernandes 为什么在using MyFunc = void(&amp;)(int,int); 中需要 (&) ?这是否意味着MyFunc 是对函数的引用?如果省略 & 会怎样?
  • 是的,它是一个函数引用。相当于typedef void (&amp;MyFunc)(int,int);。如果省略 &amp;,则相当于 typedef void MyFunc(int,int);

标签: c++ c++11 typedef using-declaration


【解决方案1】:

它们是等效的,来自标准(强调我的)(7.1.3.2):

typedef-name 也可以由别名声明引入。这 using 关键字后面的标识符成为 typedef-name 并且 标识符后的可选属性说明符序列 到那个 typedef 名称。 它具有与它相同的语义 由 typedef 说明符引入。 特别是,它 没有定义新的类型,也不会出现在 type-id 中。

【讨论】:

  • 从答案来看,using 关键字似乎是typedef 的超集。那么,typdef 将来会被弃用吗?
  • 弃用并不必然表示删除的意图——它只是作为一个非常强烈的建议来选择另一种方式。
  • 但是我想知道为什么他们不只是允许 typedef 被模板化。我记得在某处读到他们引入了using 语法正是因为typedef 语义不适用于模板。这在某种程度上与 using 被定义为具有完全相同的语义这一事实相矛盾。
  • @celtschk:在提案n1489 中谈到了原因。模板别名不是类型的别名,而是一组模板的别名。为了区分typedef,感觉需要新的语法。另外,请记住 OP 的问题是关于非模板版本之间的区别。
  • 那么为什么要引入这种冗余? 2 种用于相同目的的语法。而且我从来没有看到typdef 被弃用。
【解决方案2】:

它们基本相同,除了:

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

【讨论】:

  • 特别喜欢简单的答案并指出排版的由来。
  • @g24l 你的意思是 typedef ... 可能
  • 如果我可以问,typedef 中的 C 和 C++ 有什么区别?
  • 这不是回答问题。我已经知道这种差异并在原帖中指出。我只是问你不使用模板的情况,是否有区别。
  • 去掉“大部分”这个词会更清楚
【解决方案3】:

using 语法在模板中使用时具有优势。如果需要类型抽象,还需要保留模板参数以便以后可以指定。你应该写这样的东西。

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

【讨论】:

  • 我已经在问题中指出了这一点。我的问题是,如果您不使用模板,那么 typedef 是否有任何区别。例如,当您使用 'Foo foo{init_value};'而不是 'Foo foo(init_value)' 两者都应该做同样的事情,但不要遵循完全相同的规则。所以我想知道 using/typedef 是否存在类似的隐藏差异。
【解决方案4】:

以下所有标准参考均指N4659: March 2017 post-Kona working draft/C++17 DIS。


Typedef 声明可以用作初始化语句,而别名声明不能用作初始化语句

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

  • 语义差异:无。
  • 允许的上下文中的差异:一些(1)。

(1)除了原帖中已经提到的别名模板的例子。

语义相同

由[dcl.typedef]/2 [摘录,强调 我的]

[dcl.typedef]/2 A typedef-name 也可以由一个引入 alias-declaration。 using 关键字后面的标识符变成了 typedef-name 和 identifier 后面的可选 attribute-specifier-seq 属于该 typedef-name。 这样的 typedef-name 与 typedef 说明符引入的语义相同。 [...]

别名声明引入的typedef-name与typedef声明引入的语义相同。

允许的上下文中的细微差别

但是,这并不不暗示这两种变体在可能使用它们的上下文方面具有相同的限制。事实上,尽管是极端情况,typedef declaration 是 init-statement,因此可以在允许初始化语句的上下文中使用

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

for(typedef struct { int x; int y;} P;
//  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ init-statement
    auto [x, y] : {P{1, 1}, {1, 2}, {3, 5}}) { (void)x; (void)y; }

而alias-declaration 不是 初始化语句,因此不能在允许初始化语句的上下文中使用 p>

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

【讨论】:

  • 哇,最后我的直觉是对的!它们是有区别的!感谢您发现这种差异,这是一种非常非常狭窄的细节,可以对我使用的代码类型产生影响(不幸的是 XD)。
  • 我从未在任何地方看到过 typedef 的这种用法。从什么时候开始允许这样做以及可能的使用模式是什么?看起来对我来说有点可怕......
  • auto [x, y] : {P{1, 1}, {1, 2}, {3, 5}}) { (void)x; (void)y; } 这叫什么?我在哪里可以找到更多关于这方面的信息? (顺便说一句,它缺少一个左括号。)
  • @SouravKannanthaB 从 {…} 构造 std::initializer_list;跳过P 部分,假设它从第二个参数开始;分配给 x 和 y 的结构化绑定声明。
  • @SouravKannanthaB 左括号在前一行。
【解决方案5】:

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

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

所以,我们可以使用std::add_const_t&lt;T&gt; 代替typename std::add_const&lt;T&gt;::type

【讨论】:

  • 据我所知,在 std 命名空间内添加任何内容都是未定义的行为
  • @someonewithpc 我没有添加任何东西,它已经存在,我只是展示了一个类型名用法的示例。请查看en.cppreference.com/w/cpp/types/add_cv
【解决方案6】:

我知道原始发帖人有一个很好的答案,但是对于像我一样在这个线程上绊倒的人来说,the proposal 有一个重要的注释,我认为它为这里的讨论增加了一些价值,特别是对于 cmets 中关于如果 typedef 关键字将来会被标记为已弃用,或者因冗余/旧而被删除:

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

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

这种表示法的优点是使用已知的关键字来引入类型别名。但是,它也显示了几个缺点 [原文如此],其中在别名不是指定类型而是模板的上下文中使用已知的关键字为类型名称引入别名会造成混淆; Vec 是不是类型的别名,不应作为 typedef-name。名称Vec 是家族std::vector&lt;•, MyAllocator&lt;•&gt; &gt; 的名称——其中项目符号是类型名称的占位符。因此我们不建议使用“typedef”语法。另一方面是句子

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

可以阅读/解释为:从现在开始,我将使用Vec&lt;T&gt; 作为std::vector&lt;T, MyAllocator&lt;T&gt; &gt; 的同义词。通过这种阅读,别名的新语法似乎合乎逻辑。

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

更新 using 关键字专门用于模板,并且(正如在接受的答案中指出的那样)当您使用非模板时,using 和 typedef 在机械上是相同的,所以选择完全正确基于可读性和意图传达给程序员。

【讨论】:

    【解决方案7】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-12-01
      • 2013-09-18
      • 2021-08-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多