当前位置: 首页 > news >正文

【Modern C++ Part9】Prefer-alias-declarations-to-typedefs

条款9:优先使用声明别名而不是typedef

我有信心说,大家都同意使用STL容器是个好的想法,并且我希望,条款18可以说服你使用std::unique_ptr也是个好想法,但是我想绝对我们中间没有人喜欢写像这样std::unique_ptr<std::unordered_map<std::string, std::string>>的代码多于一次。这仅仅是考虑到这样的代码会增加得上“键盘手”的风险。

为了避免这样的医疗悲剧,推荐使用一个typedef:

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

但是typedef家族是有如此浓厚的C++98气息。他们的确可以在C++11下工作,但是C++11也提供了声明别名(alias declarations):

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

考虑到typedef和声明别名具有完全一样的意义,推荐其中一个而排斥另外一个的坚实技术原因是容易令人质疑的。这样的质疑是合理的。

技术原因当然存在,但是在我提到之前。我想说的是,很多人发现使用声明别名可以使涉及到函数指针的类型的声明变得容易理解:

	// FP等价于一个函数指针,这个函数的参数是一个int类型和// std::string常量类型,没有返回值typedef void (*FP)(int, const std::string&);      // typedef// 同上using FP = void (*)(int, const std::string&);     // 声明别名

当然,上面任何形式都不是特别让人容易下咽,并且很少有人会花费大量的时间在一个函数指针类型的标识符上,所以这很难当做选择声明别名而不是typedef的不可抗拒的原因。

但是,一个不可抗拒的原因是真实存在的:模板。尤其是声明别名有可能是模板化的(这种情况下,它们被称为模板别名(alias template)),然而typedef这是只能说句“臣妾做不到”。模板别名给C++11程序员提供了一个明确的机制来表达在C++98中需要黑客式的将typedef嵌入在模板化的struct中才能完成的东西。举个栗子,给一个使用个性化的分配器MyAlloc的链接表定义一个标识符。使用别名模板,这就是小菜一碟:

	template<typname T>                             // MyAllocList<T>using MyAllocList = std::list<T, MyAlloc<T>>;   // 等同于// std::list<T,//   MyAlloc<T>>MyAllocList<Widget> lw;                         // 终端代码

使用typedef,你不得不从草稿图开始去做一个蛋糕:

	template<typename T>                            // MyAllocList<T>::typestruct MyAllocList {                            // 等同于typedef std::list<T, MyAlloc<T>> type;        // std::list<T, };                                              // MyAlloc<T>>MyAllocList<Widget>::type lw;                   // 终端代码

如果你想在一个模板中使用typedef来完成创建一个节点类型可以被模板参数指定的链接表的任务,你必须在typedef名称之前使用typename

	template<typename T>                            // Widget<T> 包含class Widget{                                   // 一个 MyAloocList<T>private:                                        // 作为一个数据成员typename MyAllocList<T>::type list;...};

此处,MyAllocList<T>::type表示一个依赖于模板类型参数T的类型,因此MyAllocList<T>::type是一个依赖类型(dependent type),C++中许多令人喜爱的原则中的一个就是在依赖类型的名称之前必须冠以typename

如果MyAllocList被定义为一个声明别名,就不需要使用typename(就像笨重的::type后缀):

	template<typname T>                             using MyAllocList = std::list<T, MyAlloc<T>>;   // 和以前一样template<typename T>class Widget {private:MyAllocList<T> list;                         // 没有typename...                                          // 没有::type};

对你来说,MyAllocList<T>(使用模板别名)看上去依赖于模板参数T,正如MyAllocList<T>::type(使用内嵌的typdef)一样,但是你不是编译器。当编译器处理Widget遇到MyAllocList<T>(使用模板别名),编译器知道MyAllocList<T>是一个类型名称,因为MyAllocList是一个模板别名:它必须是一个类型。MyAllocList<T>因此是一个非依赖类型(non-dependent type),指定符typename是不需要和不允许的。

另一方面,当编译器在Widget模板中遇到MyAllocList<T>(使用内嵌的typename)时,编译器并不知道它是一个类型名,因为有可能存在一个特殊化的MyAllocList,只是编译器还没有扫描到,在这个特殊化的MyAllocListMyAllocList<T>::type表示的并不是一个类型。这听上去挺疯狂的,但是不要因为这种可能性而怪罪于编译器。是人类有可能会写出这样的代码。

例如,一些被误导的鬼魂可能会杂糅出像这样代码:

	class Wine {...};template<>                                 // 当T时Wine时class MyAllocList<Wine>{                   // MyAllocList 是特殊化的private:enum class WineType                      // 关于枚举类参考条款10{ White, Red, Rose };WineType type;                           // 在这个类中,type是个数据成员...};

正如你看到的,MyAllocList<Wine>::type并不是指一个类型。如果Widget被使用Wine初始化,Widget模板中的MyAllocList<T>::type指的是一个数据成员,而不是一个类型。在Wedget模板中,MyAllocList<T>::type是否指的是一个类型忠实地依赖于传入的T是什么,这也是编译器坚持要求你在类型前面冠以typename的原因。

如果你曾经做过模板元编程(TMP),你会强烈地额反对使用模板类型参数并在此基础上修改为其他类型的必要性。例如,给定一个类型T,你有可能想剥夺T所包含的所有的const或引用的修饰符,即你想将const std::string&变成std::string。你也有可能想给一个类型加上const或者将它变成一个左值引用,也就是将Widget变成const Widget或者Widget&。(如果你没有做过TMP,这太糟糕了,因为如果你想成为一个真正牛叉的C++程序员,你至少需要对C++这方面的基本概念足够熟悉。你可以同时看一些TMP的例子,包括我上面提到的类型转换,还有条款23和条款27。)

C++11给你提供了工具来完成这类转换的工作,表现的形式是type traits,它是<type_traits>中的一个模板的分类工具。在这个头文件中有数十个类型特征,但是并不是都可以提供类型转换,不提供转换的也提供了意料之中的接口。给定一个你想竞选类型转换的类型T,得到的类型是std::transformation<T>::type。例如:

    std::remove_const<T>::type                 // 从 const T 得到 Tstd::remove_reference<T>::type             // 从 T& 或 T&& 得到 Tstd::add_lvalue_reference<T>::type         // 从 T 得到 T&

注释仅仅总结了这些转换干了什么,因此不需要太咬文嚼字。在一个项目中使用它们之前,我知道你会参考准确的技术规范。

无论如何,我在这里不是只想给你大致介绍一下类型特征。反而是因为注意到,类型转换总是以::type作为每次使用的结尾。当你对一个模板中的类型参数(你在实际代码中会经常用到)使用它们时,你必须在每次使用前冠以typename。这其中的原因是C++11的类型特征是通过内嵌typedef到一个模板化的struct来实现的。就是这样的,他们就是通过使用类型同义技术来实现的,就是我一直在说服你远不如模板别名的那个技术。

这是一个历史遗留问题,但是我们略过不表(我打赌,这个原因真的很枯燥)。因为标准委员会姗姗来迟地意识到模板别名是一个更好的方式,对于C++11的类型转换,委员会使这些模板也成为C++14的一部分。别名有一个统一的形式:对于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++14std::remove_reference<T>::type              // C++11: T&/T&& -> Tstd::remove_reference_t<T>                  // 等价的C++14std::add_lvalue_reference<T>::type          // C++11: T -> T&std::add_lvalue_reference_t<T>              // 等价的C++14

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;

看到没有?不能再简单了。

要记住的东西
typedef
不支持模板化,但是别名声明支持
模板别名避免了::type
后缀,在模板中,typedef
还经常要求使用typename
前缀
C++14
C++11
中的类型特征转换提供了模板别名
http://www.dtcms.com/a/275817.html

相关文章:

  • Opencv---深度学习开发
  • 云计算三大服务模式深度解析:IaaS、PaaS、SaaS
  • 【数据结构与算法】数据结构初阶:详解顺序表和链表(四)——单链表(下)
  • 【PTA数据结构 | C语言版】后缀表达式求值
  • Transforms
  • Spring(四) 关于AOP的源码解析与思考
  • 一文理解缓存的本质:分层架构、原理对比与实战精粹
  • 别再怕 JSON!5分钟带你轻松搞懂这个程序员的好帮手
  • 鸿蒙的NDK开发初级入门篇
  • RISC-V:开源芯浪潮下的技术突围与职业新赛道 (四) 产业应用全景扫描
  • (LeetCode 面试经典 150 题 ) 209. 长度最小的子数组(双指针)
  • Ntfs!LfsFlushLfcb函数分析之while的循环条件NextLbcb的确定和FirstLbcb->LbcbFlags的几种情况
  • docker-compose方式搭建lnmp环境——筑梦之路
  • 【android bluetooth 协议分析 07】【SDP详解 2】【SDP 初始化】
  • Operation Blackout 2025: Smoke Mirrors
  • Windows符号链接解决vscode和pycharm占用C盘空间太大的问题
  • NX二次开发——导入模型是常遇见的问题(导入模型原点的确定导入模型坐标的确定)
  • BERT:双向Transformer革命 | 重塑自然语言理解的预训练范式
  • 深入理解大语言模型:从核心技术到极简实现
  • 洛谷题解 | UVA1485 Permutation Counting
  • jenkins自动化部署前端vue+docker项目
  • 前端面试宝典---项目难点2-智能问答对话框采用虚拟列表动态渲染可视区域元素(10万+条数据)
  • 自动化运维工具jenkins问题
  • Ubuntu安装Jenkins
  • java堆的创建与基础代码解析(图文)
  • Classifier guidance与Classifier-free guidance的原理和公式推导
  • 深大计算机游戏开发实验三
  • 深度学习图像分类数据集—害虫识别分类
  • 分布式数据库系统模式结构深度解析
  • Nginx 中的负载均衡策略