'typedef'和'using'之间有什么区别?

1239
我知道在C++11中,我们现在可以使用using来编写类型别名,就像typedef一样。
typedef int MyInt;

从我的理解来看,等同于:
using MyInt = int;

而这种新的语法是为了表达“模板typedef”而产生的努力的结果。
template< class T > using MyType = AnotherType< T, MyAllocatorType >;

但是,对于前两个非模板示例,标准中是否还有其他细微差别呢?例如,typedef以“弱”方式进行别名处理。也就是说,它不会创建新的类型,只会创建新的名称(在这些名称之间的转换是隐式的)。 using是否也是这样,它会生成新的类型吗?是否存在任何差异?

293
我个人更喜欢新的语法,因为它更类似于常规变量赋值,这可以提高可读性。例如,你更喜欢 typedef void (&MyFunc)(int,int); 还是 using MyFunc = void(int,int); - Matthieu M.
24
我完全同意,现在我只使用新语法。这就是我提出疑问的原因,为了确保真的没有任何区别。 - Klaim
102
@MatthieuM. 这两者是不同的。应该是typedef void MyFunc(int,int);(看起来其实还不错), 或者 using MyFunc = void(&)(int,int); - R. Martinho Fernandes
7
using MyFunc = void(&)(int,int); 中,为什么需要 (&) ?这是不是意味着 MyFunc 是一个指向函数的引用?如果省略 & 会怎样? - Rich
10
是的,它是一个函数引用。它等同于 typedef void (&MyFunc)(int,int);。如果省略&,则相当于 typedef void MyFunc(int,int); - R. Martinho Fernandes
显示剩余9条评论
8个回答

695
它们是等效的,根据标准(我强调)(7.1.3.2):
一个typedef名称也可以由别名声明引入。紧随using关键字的标识符变成typedef名称,标识符后面的可选属性说明符序列适用于该typedef名称。它具有与使用typedef说明符引入时相同的语义。特别地,它不定义新类型且不应出现在类型标识符中。

29
从答案中可以看出,using 关键字似乎是 typedef 的超集。那么,typedef 会在将来被弃用吗? - iammilind
62
弃用并不一定表示有意删除——它只是作为一个非常强烈的建议,优先考虑其他方式。 - Iron Savior
20
我在想,为什么他们不允许typedef模板化。我记得在某个地方读到过,他们引入“using”语法的原因正是因为typedef语义与模板不兼容。这与“using”被定义具有完全相同的语义有些矛盾。 - celtschk
13
原文:@celtschk: 这个原因在提案n1489中有所涉及。模板别名并不是类型的别名,而是一组模板的别名。为了区分于typedef,需要新的语法。另外,请注意问题提出者的问题是关于非模板版本之间的区别。翻译:@celtschk提到的原因已经在提案n1489中讨论过。模板别名并不是类型的别名,而是一组模板的别名。为了与typedef区分开来,需要新的语法。此外,请注意提问者的问题是关于非模板版本之间的区别。 - Jesse Good
4
为什么会出现这种冗余呢?为了实现同样的目的出现了两种语法。我看不到typedef会被废弃。 - Benoît
显示剩余5条评论

406

它们在很大程度上是相同的,除了:

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


61
特别喜欢这个答案的简洁性和指出排版的起源。 - g24l
2
@g24l 你的意思是 typedef... 可能吗? - Marc.2377
我可以问一下,在 typedef 中 C 和 C++ 有什么区别吗? - McSinyx
3
这并不回答问题。我已经知道区别并在原帖中指出。我只是在问如果您不使用模板的情况下,是否有区别。 - Klaim
1
去掉“大体上”这个词会使这更清晰。 - pm100
@McSinyx,没有区别。 - Bolpat

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

44
虽然我在问题中已经指出了这一点,但我的问题是如果您不使用模板,是否与typedef有任何区别。例如,当您使用“Foo foo{init_value};”而不是“Foo foo(init_value)”时,两者都应该执行相同的操作,但并不完全遵循相同的规则。因此,我想知道是否在使用/typedef时存在类似的隐藏差异。 - Klaim

160

以下所有标准参考均指N4659: 2017年3月Kona后工作草案/C++17 DIS


typedef声明可以作为初始化语句,而别名声明不行(+)

但是,在前两个非模板示例中,标准中是否还有其他细微差别呢?

  • 语义上的差异:没有。
  • 允许的上下文差异:一些(++)

(+) P2360R0扩展init-statement以允许alias-declaration已被CWG批准,截至C++23,typedef声明和别名声明之间的这种不一致性将被消除。
(++) 除了已在原帖中提到的别名模板示例之外。

相同的语义

根据[dcl.typedef]/2 [摘录,重点是我的]

[dcl.typedef]/2 也可以通过alias-declaration引入一个typedef-name。紧随using关键字后面的标识符成为一个typedef-name,后面的可选attribute-specifier-seq与该typedef-name有关。 通过typedef限定符引入的typedef-name具有与通过alias-declaration引入的typedef-name相同的语义。 [...]

通过alias-declaration引入的typedef-name具有与通过typedef声明引入的typedef-name相同的语义。

允许上下文中的微妙差异

然而,这并不意味着这两个变量在使用上下文的限制方面相同。实际上,虽然只是一个角落的情况,typedef声明是一个init语句,因此可以在允许初始化语句的上下文中使用。
// 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

11
哇,我的直觉最终证明是正确的!确实有一个差别!感谢您发现了这个差异,这是一种非常非常微小的细节,在我所处理的代码类型中可能会产生影响(不幸的是 XD)。 - Klaim
4
我从来没有在其他地方看到过typedef这样的用法。自从什么时候开始允许这种用法,有哪些可能的用法模式?老实说,这看起来很糟糕。 - Elmar Zander
1
这是什么?在哪里可以找到更多相关信息?(顺便说一下,它缺少一个开放的括号。)auto [x, y] : {P{1, 1}, {1, 2}, {3, 5}}) { (void)x; (void)y; } - Sourav Kannantha B
4
使用{...}构建std::initializer_list;跳过第二个参数开始的P部分,因为这是默认的;使用结构化绑定声明将值分配给x和y。 - Roman Odaisky
1
@SouravKannanthaB 左括号在上一行。 - jazzpi
显示剩余3条评论

28

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

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


据我所知,在std命名空间内添加任何内容都是未定义行为。 - someonewithpc
13
@someonewithpc,我没有添加任何内容,它已经存在,我只是展示了一个使用typename的例子。请参考https://en.cppreference.com/w/cpp/types/add_cv - Validus Oculus

16

这两个关键字是等价的,但有一些要注意的地方。首先,使用using T = int (*)(int, int);声明函数指针比使用typedef int (*T)(int, int);更清晰。其次,模板别名形式不能使用typedef。第三,公开头文件中暴露C API需要使用typedef


16
我知道原作者给出了很好的答案,但对于像我这样偶然遇到这个帖子的人来说,有一条重要的提示来自提案,我认为它对这里的讨论增加了一些价值,特别是对于评论中关于typedef关键字是否会在未来被标记为弃用或因冗余/过时而被删除的担忧:

It has been suggested to (re)use the keyword typedef ... to introduce template aliases:

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

That notation has the advantage of using a keyword already known to introduce a type alias. However, it also displays several disavantages [sic] among which the confusion of using a keyword known to introduce an alias for a type-name in a context where the alias does not designate a type, but a template; Vec is not an alias for a type, and should not be taken for a typedef-name. The name Vec is a name for the family std::vector<•, MyAllocator<•> > – where the bullet is a placeholder for a type-name.Consequently we do not propose the “typedef” syntax.On the other hand the sentence

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

can be read/interpreted as: from now on, I’ll be using Vec<T> as a synonym for std::vector<T, MyAllocator<T> >. With that reading, the new syntax for aliasing seems reasonably logical.

对我而言,这意味着在C++中继续支持typedef关键字,因为它仍然可以使代码更易读和理解。
更新using关键字是专门针对模板的,当你使用非模板时,usingtypedef在机械上是相同的(正如被接受的答案所指出的),因此选择完全取决于程序员的可读性和意图交流。

7
截至目前,C++23将更加接近typedefusingP2360提出using应构成一个init-statement,例如@dfrib的答案中列出的error: expected expression
然而,即使有P2360,typedef也不能是一个模板。
Edit[2022-09-14]:这里包含错误信息。)
总的来说,usingtypedef更为强大,并且在我看来更加易读。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接