Effective Modern C++ 条款9 用别名声明(alias declaration)代替typedef

来源:互联网 发布:xbox无法连接网络 编辑:程序博客网 时间:2024/06/11 04:15

用别名声明(alias declaration)代替typedef

我相信你像我一样觉得使用STL容器是个好主意,我希望在条款18中能让你相信使用std::unique_ptr也是个好主意,但我猜我们都不喜欢写超过一次这样的类型的代码:std::unique_ptr<std::unordered_map<std::string,std::string>>。一想到这么多的代码就觉得增加了得腕管综合征的风险。

避免得病比较简单。引进typedef

typedef   std::unique_ptr<std::unordered_map<std::string, std::string>>  UPtrMapSS;

但是typedef太太太C++98了,它虽然可以在C++11中正常工作,但是C++11提供了别名声明(alias declaration):

using UPtrMapSS =   std::unique_ptr<std::unordered_map<std::string, std::string>>;

就算typedef和别名声明的作用是一样,我们也有可靠的技术上的原因建议你使用别名声明。

在我们讲关于技术上的原因之前,我想说很多人觉得在处理函数指针时,使用别名声明比使用typedef简单:

// PF是一个参数为int和const std::string&,返回值为void的函数指针的同义词typedef void (*PF)(int, const std::string &);using PF = void (*)(int, const std::string &);

当然,这两种形式都是特别容易地被我们啃下,一些人还是需要时间来处理函数指针类型的同义词,所以说,这并不是强迫我们使用别名声明代替typedef的原因。

但强迫我们的原因还是存在的:模板。特别是别名声明可以实例化(这种情况叫做alias template),而typedef不可以。这给了C++11的开发者一种直截了当的方式来表达C++98必须把typedef嵌套在结构的事情。例如,试图定义一个链表的同义词,这个链表使用自定义的分配器MyAlloc。使用别名声明,这就是小case:

template <typename T>`using MyAllocList = std::list<T, MyAlloc<T>>;MyAllocList<Widget> lw;    // 用户代码

使用typedef的话,就比较麻烦了:

template <typename T>struct MyAllocList {   typedef std::list<T, MyAlloc<T>> type;};MyAllocList<Widget>::type lw;    // 用户代码

这种方式不好。如果你想要在模板中使用这个typedef来创建一个链表,链表的对象是模板参数类型,那么你必须在typedef同义词之前使用typename

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

这里的代码,MyAllocList<T>::type指的类型依赖于模板类型的参数(T)。因此MyAllocList<T>是一个依赖类型(dependent type),而C++一个受很多人喜欢的规则是,依赖类型之前必须紧挨着typename

如果MyAllocList被定义为alias template(别名模板),就不需要typename啦:

template <typename T>using MyAllocList = std::list<T, MyAlloc<T>>;  // 如前template <typename T>class Widget {private:  MyAllocList<T> list;   //没有typename,也没有::type  ...};

对于你,MyAllocList<T>(使用别名模板)和MyAllocList<T>::type(内嵌typedef)看起来一样,只依赖模板参数T,但你不是编译器。当编译器处理Widget模板时遇到MyAllocList<T>,它知道MyAllocList<T>是个别名模板:它一定是个类型的名字。所以MyAllocList<T>是个非依赖类型(non-dependent type),不需要也不允许关键字typename修饰。

另一方面,当编译器在Widget模板看到MyAllocList<T>::type时,它不能确定这是不是一个类型的名字,因为可能有一个MyAllocList的特例化完全没看到MyAllocList<T>::type是一个类型。这听起来很疯狂,但你不能责怪编译器担心这个可能性,人们习惯写这种代码。

例如,一些误入歧途的灵魂可能在图谋着一些这样的事情:

class Wine {...};template<>      // 模板特例化class MyAllocList<Wine> {private:  enum class WineType  // 关于枚举类,请看条款10  { White, Red, Rose };  WineType type;  // 在这个类中,type是一个成员变量  ...};

正如你所见,MyAllocList<Wine>::type指代的不是类型。如果WidgetWine实例化,那么Widget模板内的MyAllocList<T>::type指代的是一个数据成员,而不是类型。然后在Widget模板内,MyAllocList<T>::type是否指代一个类型,要依赖于参数T。这就是为什么编译器就要求你在它名字前加typename,这样做后编译器断言它是个类型,不用再等到模板实例化时才确定。

以下关于模板元编程方面的知识,由网友fesdobat翻译。

如果你做过模板元编程(templete meta programming, TMP),你肯定会遇到过要把模板类型转换为其他类型的需求。例如,对于类型T,你可能想要去除T含有的const 修饰或者引用修饰,具体来说,比如你可能会想把const string &变成std::string。或者你也有可能想把一个类型加上const修饰或者左值引用修饰,具体来说,比如把Widget变成const Widget或者Widget &。(如果你还没有碰到过模板元编程,那可真是太糟糕了,因为如果你想成为真正高效的C++程序员,你就需要对c++的这一块至少有最基本的了解。你会在条款25和条款29中也看到TMP的例子,其中也包含了我在上面提及到的类型转换。)

C++11提供了实行这样转换的工具,他们叫做type traits,是一种变形的模板类,包含在头文件<type_traits>中。这个头文件中有一系列这样的type traits,但并不是所有的都与处理类型转换相关,其中的一些提供了望文而知意的接口。假设你要转换的源类型为T,你需要的转换结果就是std::transformation<T>::type。例如:

std::remove_const<T>::type // yields T from const Tstd::remove_reference<T>::type // yields T from T& and T&&std::add_lvalue_reference<T>::type // yields T& from T

上面的注释只是简单说明代码做了什么。不要在意我的修辞手法。不过在把他们用到工程中之前,你一定去会仔细查阅他们的说明文档的,这我知道。

不过我在这里的原意也不是上一堂关于type traits的教学课。我请你注意到这些转换接口的末尾都带有“::type”的后缀。如果你用它们在模板内部作为参数传递(实际上你总是这样使用它们),你也不得不在每个前面加上一个“typename”。会出现这样像是马路两边的“路牙”的玩意,是因为c++11的type traits的实现,是基于模板化结构的内部嵌套typedef。没错,那就是我曾经极力向你推荐的类型变化技巧,现在他们输给了模板别名(alias template)!!(译注:即using关键字)

c++11这样做是有历史原因的,不过我们先跳过他们(说明很无趣,我保证)。长话短说,因为c++标准化委员会终于认识到模板别名是更好的实现方案,他们于是先在c++14里为这些c++11风格的类型转换做了一层包装。包装具有如下形式:对每个c++11形如std::transformation<T>::type的转换接口,就有一个与之对应的C++14 模板别名std::transformation_t。看例子就知道我说的意思了。

std::remove_const<T>::type // C++11: const T → Tstd::remove_const_t<T> // C++14 equivalentstd::remove_reference<T>::type // C++11: T&/T&& → Tstd::remove_reference_t<T> // C++14 equivalentstd::add_lvalue_reference<T>::type // C++11: T →T&std::add_lvalue_reference_t<T> // C++14 equivalent

c++11形式的接口在c++14里还能用,不过我想不出你还有什么理由用他们。即使你没有c++14,你自己写一个模板别名也是小菜一碟。这只需要用到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;

你瞧,简单得不能再简单了吧。

总结

需要记住的3点:

  • typedef不支持模板化,但别名声明(alias declaration)支持。
  • 别名声明(alias declaration)可以避免“::type”后缀,而且在模板中,typedef通常需求typename前缀。
  • C++14 offers alias templates for all the C++11 type traits transformations.
0 0
原创粉丝点击