条款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.pswp.cn/web/88650.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/88650.shtml
英文地址,请注明出处:http://en.pswp.cn/web/88650.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32第二十一天定时器TIM

1 定时器基础知识a:上来说就是用来定时的机器&#xff0c;是存在于STM32单片机中的一个外设。STM32总共有8个定时器&#xff0c;分别是2个高级定时器(TIM1、TIM8)&#xff0c;4个通用定时器 (TIM2、TIM3、TIM4、TIM5) 和2个基本定时器 (TIM6、TIM7)&#xff0c;如下图所示&…

鼎捷T100程序开发:校验程序详解

校验程序概述 T100系统校验程序需要确保系统数据的准确性、完整性和一致性&#xff0c;相当于企业信息系统的"健康体检医生"。它通过预设规则扫描系统数据&#xff0c;识别异常和错误&#xff0c;确保业务运行可靠。通过持续完善的校验机制&#xff0c;企业能够构建数…

BaseDao 通用查询方法设计与实现

BaseDao 通用查询方法设计与实现 一、通用查询方法设计思路 1. 核心查询功能矩阵查询类型方法名功能说明复杂度主键查询findById()根据主键获取单个实体⭐全量查询findAll()获取全部实体⭐条件查询findByCondition()动态条件查询⭐⭐⭐分页查询findPage()分页结果集⭐⭐⭐⭐排序…

llama.cpp gguf主要量化方法

量化是一种通过降低模型参数的表示精度来减少模型的大小和计算存储需求的方法&#xff0c;如把单精度fp32转化为int8来减少存储和计算成本。 常见的是线性量化&#xff0c;公式 r S(q-Z)&#xff0c;将实数值r映射为量化的整数值q&#xff0c;其中缩放因子S和零点Z根据参数分…

汽车级MCU选型新方向:eVTOL垂桨控制监控芯片的替代选型技术分析

摘要&#xff1a;随着eVTOL&#xff08;电动垂直起降航空器&#xff09;领域的蓬勃发展&#xff0c;对于高性能、高可靠性的垂桨控制监控芯片的需求日益迫切。本文旨在深入探讨汽车级MCU&#xff08;微控制单元&#xff09;在这一新兴领域的应用潜力&#xff0c;以国科安芯推出…

Deepoc具身智能大模型:送餐机器人如何学会“读心术”

Deepoc具身智能大模型&#xff1a;送餐机器人如何学会“读心术”深夜十点的商场火锅店&#xff0c;一台银色机器人正穿越喧闹的人群。当它感知到奔跑的儿童突然变向&#xff0c;驱动轮立即反向微调0.3度&#xff1b;托盘上的牛油锅底因顾客推椅产生晃动&#xff0c;平衡系统瞬间…

学习设计模式《十七》——状态模式

一、基础概念 状态模式的本质是【根据状态来分离和选择行为】。 状态模式的定义&#xff1a;允许一个对象在其内部状态改变时改变它的行为&#xff1b;对象看起来似乎修改了它的类。 认识状态模式序号认识状态模式说明1状态和行为通常指的是对象实例的属性的值&#xff1b;而行…

python的婚纱影楼管理系统

前端开发框架:vue.js 数据库 mysql 版本不限 后端语言框架支持&#xff1a; 1 java(SSM/springboot)-idea/eclipse 2.NodejsVue.js -vscode 3.python(flask/django)–pycharm/vscode 4.php(thinkphp/laravel)-hbuilderx 数据库工具&#xff1a;Navicat/SQLyog等都可以 随着婚纱…

滤波电路Multisim电路仿真实验汇总——硬件工程师笔记

目录 1 滤波电路基础知识 1.1 滤波电路的分类 1.1.1 按频率选择性分类 1.1.2 按实现方式分类 1.2 滤波电路的设计 1.2.1 确定滤波器类型 1.2.2 计算截止频率 1.2.3 选择滤波阶数 1.2.4 考虑元件参数 1.2.5 仿真验证 1.3 滤波电路的应用 1.3.1 电源滤波 1.3.2 音频…

C++随机打乱函数:简化源码与原理深度剖析

文章目录一、Fisher-Yates洗牌算法核心原理二、std::random_shuffle简化实现与缺陷分析简化源码&#xff08;核心逻辑&#xff09;原理层面的致命缺陷三、std::shuffle的现代改进与实现简化源码&#xff08;核心逻辑&#xff09;原理层面的关键改进四、随机数生成器工作原理URB…

DBeaver连接MySQL8.0报错Public Key Retrieval is not allowed

DBeaver 链接本地mysql8.0服务报错Public Key Retrieval is not allowed为什么会出现这个错误&#xff1f;MySQL 8.0 默认使用新的认证插件&#xff1a;caching_sha2_password某些客户端&#xff08;比如老版本的 JDBC 驱动或配置不当的 DBeaver&#xff09;在连接时&#xff0…

SpringBoot系列—统一功能处理(拦截器)

上篇文章&#xff1a; SpringBoot系列—MyBatis-plushttps://blog.csdn.net/sniper_fandc/article/details/148979284?fromshareblogdetail&sharetypeblogdetail&sharerId148979284&sharereferPC&sharesourcesniper_fandc&sharefromfrom_link 目录 1 拦…

《汇编语言:基于X86处理器》第7章 整数运算(3)

本章将介绍汇编语言最大的优势之一:基本的二进制移位和循环移位技术。实际上&#xff0c;位操作是计算机图形学、数据加密和硬件控制的固有部分。实现位操作的指令是功能强大的工具&#xff0c;但是高级语言只能实现其中的一部分&#xff0c;并且由于高级语言要求与平台无关&am…

应用笔记|数字化仪在医学SS-OCT中的应用

引言近些年来&#xff0c;OCT&#xff08;光学相干断层扫描&#xff0c;Optical Coherence Tomography&#xff09;作为一种非破坏性3D光学成像技术逐渐在医学眼科设备中流行起来。OCT可提供实时一维深度或二维截面或三维立体的图像&#xff0c;分辨率可达微米&#xff08;μm&…

Ubuntu 22.04与24.04 LTS版本对比分析及2025年使用建议

Ubuntu 22.04与24.04 LTS版本对比分析及2025年使用建议 在2025年的技术环境下&#xff0c;Ubuntu 22.04和24.04 LTS各有优势&#xff0c;选择哪一个取决于具体应用场景和用户需求。经过对系统内核、桌面环境、软件生态、生命周期支持等多方面因素的综合分析&#xff0c;本报告将…

Linux进程的生命周期:状态定义、转换与特殊场景

前言 在Linux系统中&#xff0c;进程是资源分配和调度的基本单位&#xff0c;而进程状态则是理解进程行为的关键。从运行中的任务&#xff08;TASK_RUNNING&#xff09;到僵尸进程&#xff08;EXIT_ZOMBIE&#xff09;&#xff0c;每个状态都反映了进程在内核调度、资源等待或父…

神经网络简介

大脑的基本计算单位是神经元&#xff08;neuron&#xff09;。人类的神经系统中大约有860亿个神经元&#xff0c;它们被大约10^14-10^15个突触&#xff08;synapses&#xff09;连接起来。下面图表的左边展示了一个生物学的神经元&#xff0c;右边展示了一个常用的数学模型。每…

多路由协议融合与网络服务配置实验(电视机实验)

多路由协议融合与网络服务配置实验文档 一、实验用途和意义 &#xff08;一&#xff09;用途 本实验模拟企业复杂网络环境&#xff0c;整合 OSPF、RIPv2 动态路由协议&#xff0c;结合 DHCP、FTP、Telnet 服务配置及访问控制策略&#xff0c;实现多区域网络互联、服务部署与…

在指定conda 环境里安装 jupyter 和 python kernel的方法

在 Conda 的指定环境中安装 Jupyter 和 Python Kernel 是一个常见操作,以下是详细步骤,确保在指定环境中正确配置 Jupyter 和 Python Kernel: 1. 准备工作 确保已安装 Anaconda 或 Miniconda,Conda 环境管理工具可用。确认已创建或计划使用的 Conda 环境。2. 步骤:安装 J…

【数据结构与算法】数据结构初阶:详解顺序表和链表(四)——单链表(下)

&#x1f525;个人主页&#xff1a;艾莉丝努力练剑 ❄专栏传送门&#xff1a;《C语言》、《数据结构与算法》、C语言刷题12天IO强训、LeetCode代码强化刷题 &#x1f349;学习方向&#xff1a;C/C方向 ⭐️人生格言&#xff1a;为天地立心&#xff0c;为生民立命&#xff0c;为…