本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误。谢谢!

博客已经迁移到这里啦

我确信我们都同意使用STL容器是一个好主意,并且我希望在Item 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和别名声明(alias declaration)做的是同样的事情,这值得我们去考虑这里是否有什么科学的原因来让我们更偏爱其中一个。

是的,但是在我说明之前,我想谈一下别的:很多人发现在涉及函数指针的时候,别名声明(alias declaration)更好接受一点:

//FP 是指向一个函数的指针的别名,这个函数以一个int和一个
//const std::string&为参数,不返回任何东西。
typedef void (*FP)(int, const std::string&); //typedef //和上面同样的意义
using FP = void(*)(int, const std::string&): //别名声明

当然,这两个都很容易接受,并且不管怎么说,很少人需要花费大量的时间学习使用函数指针类型的别名,所以这几乎不是一个强硬的理由来让我们选择别名声明(alias declaration)替换typedef

但是强硬的理由是存在的:template。尤其是,别名声明(alias declaration)能模板化(我们称之为别名模板(alias template)),但是typedef不能这么做。这给了C++11程序员一个简单的机制来表达在C++98中必须使用嵌套在模板化struct中的typedef来做的事情。举个例子,考虑定义一个链表的别名,这个链表使用自定义的分配器(allocator)MyAlloc。使用别名声明(alias declaration),这就是小case:

//MyAllocList<T>就是std::list<T, MyAlloc<T>>的别名
template<typename T>
using MyAllocList = std::list<T, MyAlloc<T>>; MyAllocList<Widget> lw; //客户代码

使用typedef,你就有麻烦了:

//MyAllocList<T>::type就是std::list<T, MyAlloc<T>>的别名
template<typename T>
struct MyAllocList{
typedef std::list<T, MyAlloc<T>> type;
}; MyAllocList<Widget>::type lw; //客户代码

如果你想在template中使用typedef来达到 使用template参数作为链表的模板类型参数 来创建别名的目的,这将变得更糟糕,你必须在typedef前面加上typename

//Widget中有一个MyAllocList<T>作为成员变量
template<typename T>
class Widget{
private:
typename MyAllocList<T>::type list;
...
};

这里,MyAllocList::type引用的是一个类型依赖于template的参数(T)。MyAllocList::type因此是一个依赖类型(dependent type),并且C++的众多”可爱“的规则中的一个就是,在依赖类型前面必须加typename

如果MyAllocList被定义为别名模板(alias template),对于typename的需求就消失了(笨重的“::type”后缀也消失了):

template<typename T>
using MyAllocList = std::list<T, MyAlloc<T>>; template<typename T>
class WidgetP
private:
MyAllocList<T> list; //没有"typename",没有"::type"
...
};

对于你,MyAllocList(也就是,使用alias template)可能看起来和MyAllocList::type(也就是,使用嵌套typedef)一样,也依赖于template参数(T),但是你不是编译器,当编译器处理Widget template,并且碰到MyAllocList的使用(也就是,使用alias template)时,它知道MyAllocList就是类型的名字,因为MyAllocList是一个alias template:它肯定代表一个类型。MyAllocList因此是一个非依赖类型(non-dependent type),并且它既不需要也不允许使用typename

另一方面,当编译器在Widget template中看到MyAllocList::type(也就是,使用嵌套typedef)时,编译器不肯定它就代表一个类型,因为编译器不知道这里是否有一个特化的MyAllocList,它的MyAllocList::type代表的不是一个类型。这听起来很疯狂,但是对于这个可能性,不要指责编译器。我们人类知道怎么制造这样的代码。

举个例子,一些误入歧途的灵魂可能像这样被制造出来:

class Wine{...};

template<>					//特化的MyAllocList
class MyAllocList<Wine>{ //特化的类型是Wine
private:
enum class WinType //关于"enum class"的信息
{ White, Red, Rose}; //请看Item 10 WineType type; //在这个类中,type是一个成员变量
...
};

就像你看到的,MyAllocList::type不是一个类型,如果WidgetWine来实例化,Widget template中的MyAllocList::type指向一个成员变量,而不是类型。因此,在Widget template中,MyAllocList::type是否代表一个类型需要依赖于T是什么,并且这也就是为什么如果你认为这是一个类型,编译器还是坚持让你在前面使用typename

如果你做过任何模板元编程(template metaprogramming (TMP)),你肯定碰到过这样的需求:使用template类型参数创造修改后的类型。举个例子,对于给定的类型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::转换::type):

std::remove_const<T>::type			//用 const T 产生 T

std::remove_reference<T>::type		//用 T& 或 T&& 产生 T

std::add_lvalue_reference<T>::type	//用 T 产生 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版本。

Item 9: 比起typedef更偏爱别名声明(alias declaration)的更多相关文章

  1. item 5: 比起显式的类型声明,更偏爱auto

    本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 啊,简单愉快的代码: int x; 等等,讨厌!我忘了初始化x,所 ...

  2. item 10: 比起unscoped enum更偏爱scoped enum

    本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 一般情况下,在花括号中声明一个name(包括变量名,函数名),这个 ...

  3. item 8: 比起0和NULL更偏爱nullptr

    本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 先让我们看一些概念:字面上的0是一个int,不是一个指针.如果C+ ...

  4. Item 13: 比起iterator优先使用const_iterator

    本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 STL中的const_iterator等价于pointers-to ...

  5. item 11: 比起private undefined function优先使用deleted function

    本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 如果你为其他开发者提供代码,并且你想阻止他们调用一个特定的函数,你 ...

  6. #include和前置声明(forward declaration)

    #include和前置声明(forward declaration) 1.    当不需要调用类的实现时,包括constructor,copy constructor,assignment opera ...

  7. Elasticsearch之索引模板index template与索引别名index alias

    为什么需要索引模板? 在实际工作中针对一批大量数据存储的时候需要使用多个索引库,如果手工指定每个索引库的配置信息(settings和mappings)的话就很麻烦了. 所以,这个时候,就存在创建索引模 ...

  8. 064——VUE中vue-router之使用路由别名定制(alias)

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  9. 宋宝华: Linux内核编程广泛使用的前向声明(Forward Declaration)

    本文系转载,著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 作者:宋宝华 来源: 微信公众号linux阅码场(id: linuxdev) 前向声明 编程定律 先强调一点:在一切可 ...

随机推荐

  1. recovery 升级过程执行自定义shell命令

    有时候我们需要,在升级的过程中,执行一些shell命令,来完成我们的一些需求,利用升级过程,进行一些特殊化的操作,思路如下: 第一: 把我们需要执行的命令,写成一个test.sh脚本,然后在recov ...

  2. ocLazyLoad按顺序加载

    $ocLazyLoad.load({ serie:true, files: [oneFile,twoFile] }) 使用serie:true 这是 传送门

  3. java面试题之----spring MVC的原理和MVC

    1.什么是mvc? 1.1原始比较初级的设计模式: 1.2 MVC设计模式 2MVC设计模式的优势与核心在于其能解耦和: 传统的设计模式相当于是一个串联的设计,只要其中一个环节出了问题便会使下一环节中 ...

  4. drop all database objects

    /*Use this sql to drop all objects in a database.*/ -- Drop all SPdeclare @dropSp varchar(max)=''sel ...

  5. Vue2 框架开发的单页程序页面首次加载慢的原因与优化方案

    在用Vue2 框架进行单页面开发时,开发完成后项目打包到线上环境,发现vendor脚本有963K,app.css文件也有四百多k,用户第一次打开网页加载这两个文件要十多秒,会使页面白屏十多秒,之后再次 ...

  6. Windows和Mac浏览器启动本地程序

    前言 这几天有个需求,需要在IE上启动本地程序,就如下面一样. 一开始,我还以为IE有提供特殊的接口,类似上图中的“RunExe”,可以找了大半天觉得不对经(找不到该方法). 后来想想不对,这种方式是 ...

  7. pandas的to_csv()使用方法

    1.首先查询当前的工作路径: import osos.getcwd() #获取当前工作路径2.to_csv()是DataFrame类的方法,read_csv()是pandas的方法dt.to_csv( ...

  8. WPF之托盘图标的设定

    首先需要在项目中引用System.Windows.Forms,System.Drawing; using System; using System.Collections.Generic; using ...

  9. EJB3.0中的session bean以及MDB解析

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/aboy123/article/details/24587133 大型业务系统面临的主要问题就是高并发 ...

  10. 最近公共祖先(LCA)模板

    第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每行包含两个正整数x.y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树). 接下来M行 ...