本文翻译自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. centos6启动服务说明

    centos6启动服务说明 阅读目录 centos6.9最小化安装下的启动服务 其他服务(仅供参考,持续更新) 此表转自:参考1.参考2.另有多处补充及纠正. 1. centos6.9最小化安装下的启 ...

  2. C#基础(string)

    https://msdn.microsoft.com/zh-cn/library/84787k22(v=vs.110).aspx 1.Compare基本方法 public static int Com ...

  3. centos7下安装指定版本mysql5.7.23

    现在mysql版本已经到MySQL 8.0(GA)稳定版本了,所以需求是想简单又快速在centos7下安装指定版本例如MySQL 5.7(GA)版本有下面这种方法 首先需要到mysql官网这里下载对应 ...

  4. Mybatis&orcale update语句中接收参数为对象

    Mybatis的 mapper.xml 中 update 语句使用 if 标签判断对像属性是否为空值. UserDTO是传过来参数的类型,userDTO是在mapperDao接口中给更新方法的参数起的 ...

  5. 解决 Mac 突然没有声音

    前言 偶尔早上上班时发现Mac突然没有声音了,不明所以.虽然重启也可以,但是也不免太麻烦了.也许是人品不太好,遇到过多次这种情况,就在快要砸了的自己mac的时候,脑中灵光一闪,难道这是因为核心音频守护 ...

  6. 17秋 软件工程 第六次作业 Beta冲刺 总结博客

    题目:团队作业--Beta冲刺 17秋 软件工程 第六次作业 Beta冲刺 总结博客 Beta冲刺过程中各个成员的贡献百分比 世强:15.5% 陈翔:14.5% 树民:12.0% 媛媛:14.0% 港 ...

  7. 使用with open语句(未完)

    来源链接 https://blog.csdn.net/testcs_dn/article/details/45719357 读文件 读写文件是最常见的IO操作.Python内置了读写文件的函数,用法和 ...

  8. 采用spring的schedule注解配置定时任务

    1 在springmvc配置文件中新增以下配置 <!-- 此处对于定时时间的配置会被注解中的时间配置覆盖,因此,以注解配置为准 --> <task:scheduled-tasks s ...

  9. Java自定义类加载和ClassPath类加载器

    1 自定义类加载器: 实现规则: 自定义类加载器,需要重写findClass,然后通过调用loadClass进行类加载(loadClass通过递归实现类的双亲委派加载) package com.dax ...

  10. P1387 最大正方形

    2018-08-16 https://www.luogu.org/problemnew/show/P1387 题意: 略. 4 4 0 0 1 1      把这个翻译成dp的形式   0 0 1 1 ...