9.使用alias declarations而非typedefs

前言

 
想必没有人愿意反复写下诸如std::unique_ptr<std::unordered_map<std::string, std::string> >的类型名,本节将介绍如何在C++11中避免这种繁琐无聊的工作。


typedef与alias declarations

 
C++98中的typedef可以有效帮助我们解决问题:

1
2
3
typedef 
std::unique_ptr<std::unordered_map<std::string, std::string> >
UPtrMapSS;

C++11提供的alias declarations也能发挥同样的作用:
1
2
using UPtrMapSS =
std::unique_ptr<std::unordered_map<std::string, std::string> >;

相对于前者,后者有一个压倒性的技术优势。


alias declarations优势

 
在讲述真正的优势之前,首先提一下很多人认为在涉及函数指针类型时alias declarations更容易接受:

1
2
3
4
// FP is a synonym for a pointer to a function 
// taking an int and a const std::string& and returning nothing
typedef void (*FP)(int, const std::string&); // typedef
using FP = void (*)(int, const std::string&); // alias declaration

但事实上,在函数指针类型方面二者并无明显优劣,而且很少有人花费很多时间处理函数指针类型,因此这并不是一个令人信服的理由来认为alias declarations强于typedef。

模板化

alias declarations真正强于typedef的一点在于,前者可以被模板化(在这种情况下我们把它称为alias template),因此使用alias declarations则不必像传统的C++98那样将typedef嵌入模板中。举例而言,假若我们需要为一个具备自定义allocator的list定义别名,使用alias declaration可以写为:

1
2
3
template<typename T>
using MyAllocList = std::list<T, MyAlloc<T>>; // is synonym for std::list<T,MyAlloc<T> >
MyAllocList<Widget> lw;

为了达到同样的效果,typedef则必须写为:
1
2
3
4
5
template<typename T>
struct MyAllocList {
typedef std::list<T, MyAlloc<T>> type;
};
MyAllocList<Widget>::type lw;

如果需要在模板中使用类型别名,那么typedef定义的别名还需要使用typename进行修饰:
1
2
3
4
5
6
template<typename T>
class Widget {
private:
typename MyAllocList<T>::type list; // as a data member

};

如果我们将MyAllocList定义为一个alias template,则不必需要各种修饰(诸如typename以及::type):
1
2
3
4
5
6
7
8
template<typename T>
using MyAllocList = std::list<T, MyAlloc<T>>;
template<typename T>
class Widget {
private:
MyAllocList<T> list; // no "typename",
// no "::type"
};

当编译器在处理Widget模板时遇到MyAllocList<T>,它会直接明确当前是一个类型,因为MyAllocList<T>是一个alias template。但当编译器在Widget模板中看到MyAllocList<T>::type(即使用嵌套的typedef)时,编译器无法确定它是否意味着一个类型(因为可能存在MyAllocList的特化),因此需要加以typename修饰。这并非编译器无理取闹,因为真的可能会有一些人将type作为class的data member:
1
2
3
4
5
6
7
8
class Wine { … };
template<>
class MyAllocList<Wine> {// MyAllocList specialization for when T is Wine
private:
enum class WineType { White, Red, Rose };// see Item10 for "enum class"
WineType type;// type is a data member

};


TMP中的alias declarations

如果你使用过TMP(模板元编程)的话,你一定遇到过需要根据模板类型参数来来创建新类型的需求,例如,你可能想要将const std::string&转换为std::string,又或者将Widget转换为const Widget或Widget&(Item23与27讲给出一些TMP实例)。
C++11以type traits的形式提供了执行这些类型转换的必要工具,这一系列模板位于头文件<type_traits>中。该头文件内存在10多个type traits,并且并非所有都提供转换操作(具备转换功能的都提供了一个预见性接口)。如果你需要转换类型,只需要如下操作:

1
2
3
std::remove_const<T>::type // yields T from const T
std::remove_reference<T>::type // yields T from T& and T&&
std::add_lvalue_reference<T>::type // yields T& from T

这里并非是传授如何在C++中使用type traits完成类型转换,我们应当把目光焦点放到上述代码最后的type之上。由于它们的性质原因,你在实际开发过程中在使用它们之前总需要冠上typename。C++11在设计type trits时将其设计为嵌套式typedef,这并非是因为嵌套式typedef比alias declarations更好用,而是由于历史遗留原因。C++14引入了新的别名模板,形如std::transformation_t,实际使用效果如下:

1
2
3
4
5
6
std::remove_const<T>::type // C++11: const T → T
std::remove_const_t<T> // C++14 equivalent
std::remove_reference<T>::type // C++11: T&/T&& → T
std::remove_reference_t<T> // C++14 equivalent
std::add_lvalue_reference<T>::type // C++11: T → T&
std::add_lvalue_reference_t<T> // C++14 equivalent

即使你的编译器不支持C++14,那使用alias declaration对type traits进行封装也并非难事,例如:
1
2
3
4
5
6
7
template <class T>
using remove_const_t = typename remove_const<T>::type;
template <class T>
using remove_reference_t = typename remove_reference<T>::type;
template <class T>
using add_lvalue_reference_t =
typename add_lvalue_reference<T>::type;


总结

  1. typedef并不支持模板化,但alias declarations支持。
  2. alias declarations不需要type、typename之类的后缀与修饰。
  3. C++14引入了alias templates以取代C++11中的type traits转换。