- ·typedef ·不支持模版化,但是
using
的别名声明可以- 模版别名避免了传统的
typedef
带来的::type
后缀,以及在类型引用的时候需要的typename
前缀C++14
给所有的C++11
模版类型萃取提供了别名
typedef
typedef - 创建能在任何位置替代(可能复杂的)类型名的别名。
typedef 名是既存类型的别名,而非对新类型的声明。
一些限制
- 在给定的作用域中,不能使用typedef说明符重新定义在该作用域中声明的任何类型的名称,以引用不同的类型。
class complex { /* ... */ };
typedef int complex; // error: redefinition
- 在给定的作用域中,类或枚举不应与在该作用域中声明并引用类或枚举本身以外的类型的typedef-name使用相同的名称声明。
typedef int complex;
class complex { /* ... */ }; // error: redefinition
- 下述示例声明了一个名为
S
使用未命名的类typedef
语法。S
被视为类名; 但是,以下限制适用于以这种方式引入的名称:
- 名称(同义词)不能出现在
class
,struct
, 或者union
字首。 - 该名称不能用作类声明中的构造函数或析构函数名称。
总之,这种语法不提供任何继承、构造或销毁的机制。
struct S {
S(){}
~S(){}
};
typedef struct S T;
int main() {
S a =T();
struct T * p; // error,'T' has a previous declaration here,修改为T*p;
return 0;
}
- 一个
typedef
不能重新定义以前声明为不同类型的名称。
// FILE1.H
typedef char CHAR;
// FILE2.H
typedef char CHAR;
// PROG.CPP
#include "file1.h"
#include "file2.h" // OK
但如果将typedef char CHAR;
修改成下述代码则错误。
// FILE2.H
typedef int CHAR;
typedef
说明符不能在不含声明符的声明中出现,例如typedef struct X {};
- typedef 说明符不能在函数形参声明中出现,也不能在函数定义中的 声明说明符序列 中出现
void f1(typedef int param); // 非良构
typedef int f2() {} // 非良构
综合示例
// 简单 typedef
typedef unsigned long ulong;
// 下面两个对象拥有同一类型
unsigned long l1;
ulong l2;
// 更复杂的 typedef
typedef int int_t, *intp_t, (&fp)(int, ulong), arr_t[10];
// 下面两个对象拥有同一类型
int a1[10];
arr_t a2;
// 避免必须写 "struct S" 的常见 C 手法
typedef struct {int a; int b;} S, *pS;
// 下面两个对象拥有相同类型
pS ps1;
S* ps2;
// 错误:存储类说明符不能出现于 typedef 声明中
// typedef static unsigned int uint;
// typedef 可用在 声明说明符序列 中的任何位置
long unsigned typedef int long ullong;
// 写作 "typedef unsigned long long int ullong;" 更符合惯例
// std::add_const,与许多其他元函数相似,使用了成员 typedef
template<class T>
struct add_const{
typedef const T type;
};
typedef struct Node{
struct listNode* next; // 声明名为 listNode 的新的(不完整)结构体类型
} listNode; // 错误:与先前声明的结构体名冲突
using
- 类型别名是指代【先前定义的类型】的名字(通过不同的语法提供与 typedef 相同的功能)。类型别名声明引入一个名字,可用做 类型标识 所指代的类型的同义词。它不引入新类型,且不能更改既存类型名的含义。
- 别名模版是指代一族类型的名字。
示例1
#include <iostream>
#include <vector>
// 创建引用的别名 int[4];
using IntArray4 = int (&)[4];
// 或者使用 typedef
// typedef int(&IntArray4)[4];
// 使用数组引用别名作为参数类型
void foo(IntArray4 array) {
// 数组上基于范围的循环
for (int i : array) std::cout << i << " ";
}
int main() {
int arr[4] = {6, 7, 8, 9};
foo(arr); // logs 6 7 8 9
return 0;
}
foo()
函数的生成代码如下
// 使用数组引用别名作为参数类型
void foo(IntArray4 array){
{
int (&__range1)[4] = array;
int * __begin1 = __range1;
int * __end1 = __range1 + 4L;
for(; __begin1 != __end1; ++__begin1) {
int i = *__begin1;
std::operator<<(std::cout.operator<<(i), " ");
}
}
}
运行结果如下:
6 7 8 9
示例2
#include <string>
#include <ios>
#include <type_traits>
// 类型别名,等同于
// typedef std::ios_base::fmtflags flags;
using flags = std::ios_base::fmtflags;
// 名字 'flags' 现在指代类型:
flags fl = std::ios_base::dec;
// 类型别名,等同于
// typedef void (*func)(int, int);
using func = void (*) (int, int);
// 名字 'func' 现在指代函数指针:
void example(int, int) {}
func f = example;
// 别名模板
template<class T>
using ptr = T*;
// 名字 'ptr<T>' 现在是指向 T 的指针的别名
ptr<int> x;
// 用于隐藏模板形参的别名模版
template<class CharT>
using mystring = std::basic_string<CharT, std::char_traits<CharT>>;
mystring<char> str;
// 别名模板可引入成员 typedef 名
template<typename T>
struct Container { using value_type = T; };
// 可用于泛型编程
template<typename ContainerType>
void g(const ContainerType& c) { typename ContainerType::value_type n; }
// 用于简化 std::enable_if 语法的类型别名
template<typename T>
using Invoke = typename T::type;
template<typename Condition>
using EnableIf = Invoke<std::enable_if<Condition::value>>;
template<typename T, typename = EnableIf<std::is_polymorphic<T>>>
int fpoly_only(T t) { return 1; }
struct S { virtual ~S() {} };
int main()
{
Container<int> c;
g(c); // Container::value_type 将在此函数中是 int
// fpoly_only(c); // 错误:被 enable_if 禁止
S s;
fpoly_only(s); // OK:被 enable_if 允许
}
编译后的代码如下:
#include <string>
#include <ios>
#include <type_traits>
// 类型别名,等同于
// typedef std::ios_base::fmtflags flags;
using flags = std::ios_base::fmtflags;
// 名字 'flags' 现在指代类型:
flags fl = std::ios_base::dec;
// 类型别名,等同于
// typedef void (*func)(int, int);
using func = void (*) (int, int);
// 名字 'func' 现在指代函数指针:
void example(int, int)
{
}
using FuncPtr_16 = func;
FuncPtr_16 f = example;
// 别名模板
template<class T>
using ptr = T*;
// 名字 'ptr<T>' 现在是指向 T 的指针的别名
ptr<int> x;
// 用于隐藏模板形参的别名模版
template<class CharT>
using mystring = std::basic_string<CharT, std::char_traits<CharT>>;
mystring<char> str = std::basic_string<char, std::char_traits<char>, std::allocator<char> >();
// 别名模板可引入成员 typedef 名
template<typename T>
struct Container { using value_type = T; };
/* First instantiated from: insights.cpp:48 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct Container<int>
{
using value_type = int;
// inline constexpr Container() noexcept = default;
};
#endif
// 可用于泛型编程
template<typename ContainerType>
void g(const ContainerType& c) { typename ContainerType::value_type n; }
/* First instantiated from: insights.cpp:49 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void g<Container<int> >(const Container<int> & c)
{
typename Container<int>::value_type n;
}
#endif
// 用于简化 std::enable_if 语法的类型别名
template<typename T>
using Invoke = typename T::type;
template<typename Condition>
using EnableIf = Invoke<std::enable_if<Condition::value>>;
template<typename T, typename = EnableIf<std::is_polymorphic<T>>>
int fpoly_only(T t) { return 1; }
/* First instantiated from: insights.cpp:52 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
int fpoly_only<S, void>(S t)
{
return 1;
}
#endif
struct S
{
inline virtual ~S() noexcept
{
}
// inline constexpr S() noexcept = default;
// inline constexpr S(const S &) noexcept = default;
};
int main()
{
Container<int> c = Container<int>();
g(c);
S s = S();
fpoly_only(S(s));
return 0;
}
区别
using | typedef |
---|---|
c++ 中的using 也称为别名声明,它可以被模式化,用于创建泛型模板。这些模板今后可以根据具体需求进行修改。 | c++ 中的typedef 不能像using 那样被模板化 |
有了c++ 中的using ,以后修改代码就更容易了,而不会影响到更高层次的代码 | 由于在c++ 中使用了 typedef ,在后期修改代码就变得相对困难了。 |
创建泛型类型别名很容易 | 使用’ typedef '创建一个通用别名需要将其包装在结构中 |
using 语句在c++ 中不是一个初始化语句,所以在允许使用初始化语句的地方不使用。 | 在c++ 中,typedef 是一个初始化语句,所以它被用于允许初始化语句的地方。 |
在c++ 中使用using 语句的优点之一是,声明函数指针与类型定义比较时非常清晰。 | 使用typedef 声明函数指针不像using 那么清晰。 |
在c++ 中使用using 语句创建别名模板不需要任何typename 。 | typedef 要求把typename 放在它的声明前面,就像包装好的指令一样。 |
使用using 语句创建别名有助于开发干净、可理解和可读的代码。 | 虽然使用 typedef 创建别名并不困难,但从程序员的角度来看,它并没有那么清晰和可读。 |
使用using 语句声明的语法: c template<[template-parameters (T1, T2…)]> using [alias] = [original-type]; | 使用typedef ' 语句声明的语法:c typedef struct struct_type short_type_t; |
c++
中using
语句和typedef
语句的一些关键区别如下:
-
using
可以模板化(这种情况下他们被称为别名模板),而typedef
不可以 -
从程序员的角度来看,使用
using
语句是非常容易和清晰的,尤其是在使用函数指针及其别名定义时。
typedef void(*func_pointer)(int);
using func_pointer = void(*)(int);
- 对于泛型别名,在
typedef
的情况下,声明需要包装在结构中,不像using
语句,它工作非常容易和清晰,不需要包装它。让我们通过下面的例子来理解这一点:
- 示例1:
template<typename T>
using Accounts = std::unordered_map<Student_ID, std::vector<T>>;
使用typedef
如下:
template<typename T>
struct Accounts {
typedef std::map<Student_ID, std::vector<T>> type;
};
//Using the above like:
Accounts<StudentAccount>::type StudentDetailsAccounts;
-
示例2:
当在模板中使用时,
using
语法具有优势。如果您需要类型抽象,但也需要保留模板参数,以便将来可以指定。你应该这样写。
template <typename T> struct whatever {};
template <typename T> struct rebind{
typedef whatever<T> type; // 使得未来有可能取代任何东西。
};
rebind<int>::type variable;
template <typename U> struct bar { typename rebind<U>::type _var_member; }
使用using
简化后的代码如下:
template <typename T> using my_type = whatever<T>;
my_type<int> variable;
template <typename U> struct baz { my_type<U> _var_member; }
- 对于
typedef
声明,可以重新声明。可以在2个或更多不同的文件中使用typedef
声明相同的变量,直到两个文件都引用相同的类型时才会抛出错误。 - 在c++中,
typedef
允许程序员一次声明多个类型,这与using
语句不同。例如:typedef int X, *ptr, (*Func)();
。 - 初始化时的细微区别:
typedef
声明 是一个init-statement
,因此可以在允许初始化语句的上下文中使用
#include <iostream>
#include <vector>
int main() {
// C++11 (C++03) (init. statement in for loop iteration statements).
for (typedef int Foo; Foo{} != 0;) {//init-statement
}
// C++17 (If和switch初始化语句).
if (typedef int Foo; true) {//init-statement
(void)Foo{};
}
switch (typedef int Foo; 0) {//init-statement
case 0:
(void)Foo{};
}
// C++20 (基于范围的for循环初始化语句).
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;
}
return 0;
}
而别名声明不是
init-statement
,因此不能在允许初始化语句的上下文中使用
#include <iostream>
#include <vector>
int main() {
// C++ 11.
for (using Foo = int; Foo{} != 0;) {
}
// ^^^^^^^^^^^^^^^ error: expected expression
// C++17 (switch和if语句中的初始化表达式).
if (using Foo = int; true) {
(void)Foo{};
}
// ^^^^^^^^^^^^^^^ error: expected expression
switch (using Foo = int; 0) {
case 0:
(void)Foo{};
}
// ^^^^^^^^^^^^^^^ error: expected expression
// C++20 (基于范围的for循环初始化语句).
std::vector<int> v{1, 2, 3};
for (using Foo = int; Foo f : v) {
(void)f;
}
// ^^^^^^^^^^^^^^^ error: expected expression
return 0;
}
编译报错如下:
/home/insights/insights.cpp:5:10: error: expected expression
for (using Foo = int; Foo{} != 0;) {
^
/home/insights/insights.cpp:5:27: error: use of undeclared identifier 'Foo'
for (using Foo = int; Foo{} != 0;) {
^
/home/insights/insights.cpp:10:9: error: expected expression
if (using Foo = int; true) {
^
/home/insights/insights.cpp:10:30: error: expected ';' after expression
if (using Foo = int; true) {
^
;
/home/insights/insights.cpp:10:26: warning: expression result unused [-Wunused-value]
if (using Foo = int; true) {
^~~~
/home/insights/insights.cpp:10:30: error: expected expression
if (using Foo = int; true) {
^
/home/insights/insights.cpp:23:10: error: expected expression
for (using Foo = int; Foo f : v) {
^
/home/insights/insights.cpp:23:27: error: unknown type name 'Foo'
for (using Foo = int; Foo f : v) {
^
/home/insights/insights.cpp:23:35: error: use of undeclared identifier 'v'
for (using Foo = int; Foo f : v) {
^
1 warning and 8 errors generated.
为何优选using
- 代码清晰
- 支持模板化
- 在定义更复杂的模板别名、函数指针别名和数组引用别名时
using
更具优势 - 减少腕管综合症的发生
参考
[1] The typedef specifier
[2] typedef 声明
[3] 类型别名,别名模版 (C++11 起)
[4] How C++ ‘using’ or alias-declaration is better than typedef
[5] Aliases and typedefs (C++)