C++ / a working model

11 / 80   ·   C++11   ·   约 7 分钟

typedef 与 using:类型别名不是新类型

先记住这句话

typedef 和 using 都为已有类型提供名字,不创建独立类型,也不是宏替换。using 的语法更适合复杂类型和别名模板;给指针别名添加 const 会限定整个指针类型,而不是自动限定它指向的对象。

本篇内容
  1. 改名字,不改类型身份
  2. 别名按整体参与限定
  3. using 让泛型表达更直接
  4. 运行示例
  5. 动手练习

改名字,不改类型身份

typedef unsigned long Count;using Count = unsigned long; 在类型别名语义上相同。Count 与 unsigned long 是同一个类型,因此不能分别定义两个仅在这两个参数名义类型上不同的重载。编译器不会因别名叫 UserId 就阻止你传入 OrderId。

如果需要业务层面的区分,应定义包装类或适当的枚举类,让类型系统看到真正不同的类型。别名适合缩短复杂声明、公开库接口中的类型名,或者隔离某些实现选择,但不能承担单位安全和身份隔离的职责。

别名按整体参与限定

using Pointer = int*; 后的 const Pointer 是 const 指针,即 int* const,而不是 const int*。这是类型组合,不是把源代码中的 Pointer 文本替换为 int* 后重新解析。示例用类型特征确认这件事,并合法修改所指的非 const 整数。

类似地,Pointer a, b; 中 a 和 b 都是指针;宏 #define POINTER int* 展开后的 POINTER a, b; 则只有 a 是指针。宏参与的是声明记号拼接,类型别名参与的是真正的类型系统,混淆两者会制造难以发现的声明差异。

using 让泛型表达更直接

函数指针可以写成 using Handler = int (*)(int);,名字在左边,完整类型在右边,比把名字嵌入 typedef 声明符更容易阅读。C++11 还允许 template<class T> using Sequence = std::vector<T>;,把一族类型命名为别名模板。

别名模板本身不能像类模板那样直接偏特化;需要条件选择时,可以借助可特化的类模板或类型工具。也要区分 using Alias = Tusing std::swapusing namespace std,它们分别是类型别名、引入声明和命名空间指令,并非同一种机制。

容易答错的地方

  • 更改公共别名的底层类型可能改变重载选择、对象布局和 ABI;别名不是兼容性屏障。
  • 用两个 int 别名分别表示米和秒,仍然允许相互赋值;要拒绝混用必须定义不同类型。

运行一个例子

最低标准 C++11 · 完整程序 · 下载 .cpp

#include <cassert>
#include <iostream>
#include <type_traits>
#include <vector>

using Pointer = int*;
template<class T> using Sequence = std::vector<T>;

int main() {
    int value = 1;
    const Pointer pointer = &value;
    static_assert(std::is_same<decltype(pointer), int* const>::value, "const pointer");
    *pointer = 8;
    Sequence<int> values{value, 2};
    assert(value == 8);
    assert(values.size() == 2);
    std::cout << values[0] << ' ' << values.size() << '\n';
}

在本地编译

g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread basics-typedef-using.cpp -o example && ./example

预期结果

8 2

CHECK YOUR UNDERSTANDING

合上答案,试着解释。

using UserId = int; using OrderId = int; 能否通过 void find(UserId) 和 void find(OrderId) 建立两个重载?给出最小改进。

查看参考答案

不能,两次声明拥有完全相同的参数类型。可定义 struct UserId { int value; }; 和 struct OrderId { int value; };,此时它们是两个类类型,两个重载才能区分。根据接口需要,再给包装类型添加 explicit 构造函数和范围校验,而不是期待别名提供隔离。

继续查证

标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。

回到目录