28. 模板别名:using别名与typedef的区别、别名模板

说到类型别名,很多C++老手第一反应就是typedef。嗯,这玩意儿确实陪伴了我们几十年。但C++11引入的using别名声明,说实话,才是真正让我觉得「这才对嘛」的特性。今天咱们就好好聊聊这个。

typedef的局限

先说说typedef。它的语法是这样的:

typedef unsigned int uint_t;
typedef void (*FuncPtr)(int, double);
typedef std::map<std::string, std::vector<int>> StringToIntVec;

看着还行对吧?但问题出在可读性上。尤其是函数指针那行,void (*FuncPtr)(int, double)——你想想看,变量名被夹在中间,读起来特别别扭。我在项目中见过不少新人盯着这种声明发呆半天。

更麻烦的是模板。你想给一个模板类起别名?typedef做不到。你得用struct包装一下:

template<typename T>
struct MyVec {
    typedef std::vector<T, MyAlloc<T>> type;
};

// 使用时
MyVec<int>::type vec;

每次都要写::type,烦不烦?我当年维护一个老项目,满屏都是这种写法,看得眼睛疼。

using别名声明

C++11的using解决了所有这些问题。语法非常直观:

using uint_t = unsigned int;
using FuncPtr = void(*)(int, double);
using StringToIntVec = std::map<std::string, std::vector<int>>;

注意看,等号左边是名字,右边是类型。符合人的阅读习惯——从左到右,名字在前,类型在后。我个人习惯在所有新代码里都用using,除非要兼容C++03的老编译器。

核心区别:

  • typedef:类型名写在中间,可读性差
  • using:类型名写在左边,等号右边是类型,清晰直观
  • using支持模板别名,typedef不支持

别名模板:真正的杀手锏

这才是using最亮眼的地方。你可以直接给模板起别名:

template<typename T>
using MyVec = std::vector<T, MyAlloc<T>>;

// 使用
MyVec<int> vec;  // 干净利落

不用再写::type了。不用再套一层struct了。代码瞬间清爽很多。

我曾经在一个大型游戏引擎项目里,用别名模板统一了所有容器的分配器。原来代码里散布着各种std::vector<T, PoolAllocator<T>>,后来我改成:

template<typename T>
using PoolVec = std::vector<T, PoolAllocator<T>>;

改完之后,代码量减少了将近20%,而且后续要换分配器,只需要改一行。

实际应用场景

别名模板最常见的用途是简化复杂类型。比如在元编程中,我们经常需要获取某个类型的特征:

// 传统写法
template<typename T>
struct remove_const {
    using type = T;
};

template<typename T>
struct remove_const<const T> {
    using type = T;
};

// 使用
typename remove_const<const int>::type x = 42;

每次都要写typename ... ::type,又长又容易漏。C++14标准库干脆提供了别名模板:

template<typename T>
using remove_const_t = typename remove_const<T>::type;

// 使用
remove_const_t<const int> x = 42;

看到没?加个_t后缀,世界清净了。标准库从C++14开始,所有类型萃取都提供了对应的_t版本。我个人建议你写新代码时直接用这些_t版本,少写一堆typename::type

小技巧:如果你自己写类型萃取,也建议遵循这个惯例——提供一个type成员,再加一个_t别名模板。这样使用者可以自由选择。

避坑指南

我曾经踩过一个坑:别名模板不会实例化模板。什么意思呢?看这个例子:

template<typename T>
struct Foo {
    static_assert(sizeof(T) > 10, "T too small");
};

template<typename T>
using Bar = Foo<T>;

Bar<int>* ptr;  // 这行不会触发static_assert!

因为Bar<int>*只是声明了一个指针,并没有真正实例化Foo<int>。只有当你真正使用Foo<int>时,断言才会触发。这个特性在某些场景下很有用,但也容易让人困惑。

注意:别名模板本质上是一个别名,不是新类型。它和原类型完全等价,可以互相赋值、转换。这和typedef一样,不会产生新的类型。

知识体系图

下面这张图总结了本章的核心内容:

类型别名:using vs typedef typedef using 别名声明 语法:typedef 类型 别名; 不支持模板别名 可读性差(函数指针等) 语法:using 别名 = 类型; 支持别名模板 可读性好,符合直觉 建议:新代码优先使用 using

总结

说白了,using别名声明就是typedef的现代化替代品。它更清晰、更强大,尤其是别名模板这个特性,让模板元编程的代码简洁了不少。如果你还在用typedef写新代码,我建议你试试using——用上之后你就回不去了。

嗯,今天就聊到这儿。记住一句话:能用using就别用typedef,除非你非得兼容C++03。