Item 9:Prefer alias declarations to typedefs

没有人会想重复写这样的代码std::unique_ptr<std::unordered_map<std::string, std::string>。当然我们可以这样写:
typedef std::unique_ptr<std::unordered_map<std::string, std::string> UPtrMapSS;
但typedef的C++98气味太浓了。C++11提供了类型别名:
using UPtrMapSS = std::unique_ptr<std::unordered_map<std::string, std::string> ;
好像差不多,是否有更强有力的技术理由能证明using更好呢,来看看函数指针的定义:

// 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 same meaning as above
using FP = void (*)(int, const std::string&); // alias declaration

差别好像也不大,有没有更牛逼的理由呢?有,模板!别名声明可以模板化(也被称为别名模板,alias templates),而typedef不行。这给予C++11程序员一种直截了当的表达机制,用以表达在C++98中不得不用嵌套在模板化的struct里的typedef才能硬搞出来的东西。比如,想要为一个应用自定义allocator(MyAlloc)的链表定义一个“同义词”,应用别名模板,很简单:

template<typename T> // MyAllocList<T>
using MyAllocList = std::list<T, MyAlloc<T>>; // is synonym for std::list<T, MyAlloc<T>>

MyAllocList<Widget> lw; // client code

如果用typedef,就不得不从头开始了:

template<typename T>                             // MyAllocList<T>::type
struct MyAllocList {                             // is synonym for
    typedef std::list<T, MyAlloc<T>> type;       // std::list<T, MyAlloc<T>>
};

MyAllocList<Widget>::type lw;     // client code

更糟糕的是,如果你想在模板内部使用typedef来创建一个链表,它容纳的对象类型由模板参数指定的话,你就必须在typedef指定的那个名字前加一个typename的前缀:

template<typename T>
class Widget {                                 // Widget<T> contains
private:                                       // a MyAllocList<T>
    typename MyAllocList<T>::type list;        // as a data member
// ... 
};

上面的MyAllocList::type代表了一个依赖于模板参数类型(T)的类型。所以MyAllocList::type被称为带依赖类型,在C++中众多讨巧规则中有一条就是带依赖类型的前面必须加上typename。
如果MyAllocList被定义为一个别名模板,那么就不需要什么typename(也不需要“::type”这个后缀了):

template<typename T>
using MyAllocList = std::list<T, MyAlloc<T>>; // as before

template<typename T>
class Widget {
private:
    MyAllocList<T> list;                     // no "typename",
// ...                                       // no "::type"
};

MyAllocList看起来可能和依赖模板参数类型的MyAllocList::type一样,但是编译器处理Widget并遇到MyAllocList的应用时(即别名模板的运用),它知道MyAllocList是一个类型的名字,因为MyAllocList是一个别名模板:它一定命名了一个类型。因此,MyAllocList是一个非依赖的类型,而typename不行。
当编译器在Widget模板中看到MyAllocList::type(即typedef的嵌套应用)时,它无法确定MyAllocList::type命名了一个类型,因为在MyAllocList的特化中,MyAllocList::type可能代表的是别的东西而不是一个类型。这听起来有点扯,但是真不怪编译器。因为人们真的可能会写出这样的代码:

class Wine { // ... };
template<>                                // MyAllocList specialization
class MyAllocList<Wine> {                 // for when T is Wine
private:
    enum class WineType                   // see Item 10 for info on "enum class"
    { White, Red, Rose };                 
    
    WineType type;                        // in this class, type is a data member!
    // ...                                 
};

如上所示,MyAllocList::type表示的并不是一个类型。如果Widget模板用Wine去特化,则其中的MyAllocList::type表示的就是一个数据成员而非类型了。在Widget模板中,MyAllocList::type是否代表一个类型的的确确依赖于T是什么,这就是为什么编译器要求通过前面加一个typename来判定的原因。
在做模板元编程时,我们可能会碰到这样的需求:根据模板类型参数,创建一个其修改后的类型。举个例子,对于给定的类型T,你可能想要去掉T上的const或引用属性。比如,你可能想要把const std::string&变成std::string。或者你可能想要增加const给一个类型,或者把它变成一个左值引用。比如,把Widget变成const Widget或者Widget&。(如果你不知道任何TMP,那太糟糕了,因为如果你想要成为一个真正厉害的C++程序员,你至少需要熟悉C++在这方面(TMP)的基础。在Item 23和27中,你能看到TMP的例子,包含我提及的类型转换。)
在头文件<type_traits>的各种模板中,C++11提供了工具,让你以type traits的形式来做这些转换。头文件中有许多type traits,但不是全都用来做类型转换的,但是它提供一些可预测的接口,给出一个你想转换的类型T,结果的类型就是std::transformation::type,例如:

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

注释只是总结了这些转换做了什么,所以使用时不要过于随便。在工程中使用它们前,我知道你会先看一下参考手册的。
不管怎么说,我的目的不是给你一个type traits的引导,而是强调这些转换在使用时需要在后面写上“::type”。如果你在template中把它们应用于template类型参数(你常常需要在代码中这样使用),你还要在前面加上typename。原因是,在C++11的type traits中,这些语法使用嵌套于模板化struct中的typedef使用。好的,就是因为这样的别名实现的技术,我想让你知道它不如别名模板(alias template)!;
C++11中这么实现是有历史原因的,但是我们不去讨论它(我保证这很无聊),因为C++标准委员会很迟才认识到alias templates才是最好的做法,并且对于C++11中的所有转换,他们在C++14中包含了所有的alias template版本。所有的别名都有一样的形式:每个C++11的转换std::transformation::type,在C++14中同样有alias template以std::transformation_t命名。例子将解释我说的:

std::remove_const<T>::type//C++11: const T -> T
std::remove_const_t<T>//C++14 等价的操作

std::remove_reference<T>::type // C++11: T&/T&& → T
std::remove_reference_t<T> // C++14 等价的操作

std::add_lvalue_reference<T>::type // C++11: T → T&
std::add_lvalue_reference_t<T> // C++14 等价的操作

C++11版本的转换在C++14中仍然有效,但是我不知道你有什么理由去使用它们。甚至如果你没有使用C++14,自己写一份alias template就像玩一样。只需要C++11的语言特性,甚至连小孩都能模仿这种模式,是吧?如果你碰巧有一份C++14标准的电子稿,将变得更加简单,因为要做的事就只有拷贝和粘贴。这里,我给你一个开头:

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;

看到了吗,没有比这更简单的事了。
你要记住的事:

  • typedef不支持模板化,但是别名声明(alias declaration)支持;
  • alias templates避免了“::type”后缀,以及在template中“typename”前缀(当代表一个类型时)的使用;
  • C++14提供所有C++11 type traits 转换的alias templates版本;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值