Item 9: 比起typedef更偏爱别名声明(alias declaration)
本文翻译自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不是一个类型,如果Widget用Wine来实例化,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)的更多相关文章
- item 5: 比起显式的类型声明,更偏爱auto
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 啊,简单愉快的代码: int x; 等等,讨厌!我忘了初始化x,所 ...
- item 10: 比起unscoped enum更偏爱scoped enum
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 一般情况下,在花括号中声明一个name(包括变量名,函数名),这个 ...
- item 8: 比起0和NULL更偏爱nullptr
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 先让我们看一些概念:字面上的0是一个int,不是一个指针.如果C+ ...
- Item 13: 比起iterator优先使用const_iterator
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 STL中的const_iterator等价于pointers-to ...
- item 11: 比起private undefined function优先使用deleted function
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 如果你为其他开发者提供代码,并且你想阻止他们调用一个特定的函数,你 ...
- #include和前置声明(forward declaration)
#include和前置声明(forward declaration) 1. 当不需要调用类的实现时,包括constructor,copy constructor,assignment opera ...
- Elasticsearch之索引模板index template与索引别名index alias
为什么需要索引模板? 在实际工作中针对一批大量数据存储的时候需要使用多个索引库,如果手工指定每个索引库的配置信息(settings和mappings)的话就很麻烦了. 所以,这个时候,就存在创建索引模 ...
- 064——VUE中vue-router之使用路由别名定制(alias)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 宋宝华: Linux内核编程广泛使用的前向声明(Forward Declaration)
本文系转载,著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 作者:宋宝华 来源: 微信公众号linux阅码场(id: linuxdev) 前向声明 编程定律 先强调一点:在一切可 ...
随机推荐
- 转:SQL Server - 使用 Merge 语句实现表数据之间的对比同步
表数据之间的同步有很多种实现方式,比如删除然后重新 INSERT,或者写一些其它的分支条件判断再加以 INSERT 或者 UPDATE 等.包括在 SSIS Package 中也可以通过 Lookup ...
- Apache的配置详解 带图
对Apache 的 Http.conf 各项配置详解 1.01 ServerRoot 配置 [ServerRoot "" 主要用于指定 Apache 的安装路径,此选项参数值在安装 ...
- django项目中在settings中配置静态文件
STATICFILES_DIRS = [ os.path.join(BASE_DIR,'static'), ] 写成大写可能看不太懂,但是小写的意思非常明显:staticfiles_dir = [ o ...
- Axure RP Pro7.0的key注册码加汉化非破解
上次我们刚分享过Axure RP Pro6.5 key注册码加汉化非破解,我还要分享一个Axure RP Pro7.0的key注册码加汉化,非破解哦. 当然方法还是不变,先用下面的密钥激活.用户名就是 ...
- UVA506-System Dependencies(拓扑序)
Problem UVA506-System Dependencies Accept:285 Submit:2824 Time Limit: 3000 mSec Problem Description ...
- oracle 11gR2 ASM添加和删除磁盘
一.环境 oracle 11gR2 RAC + Oracle Linux Server release 5.9 二.实施 备注:安全起见,操作之前停数据库实例.ASM实例 1.节点1.2磁盘信息 -- ...
- 【Java123】Java基础知识点
https://github.com/xbox1994/2018-Java-Interview 虽说不为面试做准备,仅仅就工作中遇到的很多Java问题,总是模棱两可的擦肩而过,真不是自己的风格. 还是 ...
- oracle 索引的几种方式
一.查询索引的高度 select index_name,blevel,leaf_blocks,num_rows,distinct_keys,clustering_factorfrom user_ind ...
- (转)Docker磁盘垃圾清理
文章转自https://mp.weixin.qq.com/s/S8ZjGZF8oLC8c1JRnkE5yw?tdsourcetag=s_pctim_aiomsg 1.整体分析 对于Docker来说,存 ...
- Web安全入门学习
前言 最近在公司做Linux下的PHP开发,所以乘此机会学习了下Web安全,了解了常见的Web安全漏洞,以及对应的防御方法(使用DVWA进行学习). 暴力破解 场景 在类似用户登陆界面 ...