| |
|
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
| -> C++知识库 -> 《Modern Effective C++》学习笔记5 右值引用,移动语义和完美转发 -> 正文阅读 |
|
|
[C++知识库]《Modern Effective C++》学习笔记5 右值引用,移动语义和完美转发 |
第五章:右值引用,移动语义和完美转发移动语义:提供了比复制操作代价低得多的移动操作,提升了代码的性能; 完美转发:使得人们可以撰写接受任意实参的函数模板,并将其转发到其他函数,目标函数会接受到与转发函数所接受的完全相同的实参。 条款二十三:理解std::move和std::forward这两个函数看起来是函数,其实它们在运行期间什么也不做,也不生成任何可执行代码。 std::move并不进行任何移动,它只是无条件地将实参强制转换成右值。 std::forward也不进行任何转发,它只是在某个特定条件下才执行右值强制转换,具体条件见下面代码: ?// 处理左值
?void process(const int& lvalArg) {
? ?std::cout << "lvalue process" << std::endl;
?}
?// 处理右值
?void process(int&& rvalArg) {
? ?std::cout << "rvalue process" << std::endl;
?}
??
?template<typename T>
?void logAndProcess(T&& param) {
? ?std::cout << "log here" << std::endl;
? ?// 函数形参都是左值,只有加上forward才能保持右值的类型
? ?process(std::forward<T>(param));
?}
??
?int main() {
? ?int a = 0;
? ?logAndProcess(std::move(a));
?} // 输出"rvalue process"
条款二十四:区分万能引用和右值引用T&&? T&&会有两种含义,一种是右值引用,一种是万能引用。 右值引用:仅会绑定到右值,主要用于识别一个可移对象。 万能引用:可以绑定到几乎任何类型 怎么找万能引用? 万能引用通常在两个场景下现身
以上两种场景有个共同的特点,他们都涉及类型推导。除了涉及类型推导之外,声明的形式必须是“T&&”这种格式才会是万能引用,所以以下代码都不符合条件: ?// 不是严格的T&&格式
?template<typename T>
?void f(std::vector<T>&& param);
??
?// const 同样改变了格式
?template<typename T>
?void f(const T&& param);
??
?// 特化的时候x的类型已经确定了
?template<class T, class Allocator = allocator<T>>
?class vector {
?public:
? ?void push_back(T&& x)
?};
反而是push_back的近亲emplace_back反而符合要求 ?template<class T, class Allocator = allocator<T>>
?class vector {
?public:
? ?template<class... Args>
? ?void emplace_back(Args&&... args);
?};
条款二十五:针对右值引用实施std::move,针对万能引用实施std::forward如果函数中多次使用到右值引用或者万能引用,而你想保证完成该对象的所有操作之前,其值不能被移走,就得在最后一次使用该引用时,才对其使用std::move或者std::forward。 虽然std::move和std::forward这么好,但是它们也有可能会阻止返回值优化这一机制。 返回值优化(RVO) 返回值优化就是可以将变量直接构建在返回值的内存上,避免返回时的复制操作,例如以下代码: ?Widget makeWidget() {
? ?Widget w;
? ?return w;
?}
支持返回值优化要符合以下两个条件:
而在返回时添加std::move就会破坏这个条件,导致组织了返回值优化。 条款二十六:避免依万能引用型别进行重载由于万能引用的优先级比隐式转换后匹配的优先级高,故如果使用万能引用进行重载,可能会优先调用万能引用版本的函数,造成意外的结果。 完美转发构造函数的问题尤其严重,因为对于非常量左值类型而言,它们一般都比复制构造函数更优先匹配到,而且也比基类中的复制和移动构造函数优先级高。 条款二十七:熟悉依万能引用型别进行重载的替代方案
?class Person {
?public:
? ?template<
? ? ?teypename T,
? ? ? ? ? ? ? ? ?// 打开开关
? ? ?typename = typename std::enable_if<
? ? ? ? ? ? ? ? ? ?// 判断类型是否相同
? ? ? ? ? ? ? ? ? !std::is_same<Person,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 去掉引用,const,volatile等修饰词
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? typename std::decay<T>::type
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?>::value
? ? ? ? ? ? ? ? >::type
? ?>
? ?explicit Person(T&& n);
?};
??
?// 支持基类版本
?class Person {
?public:
? ?template<
? ? ?teypename T,
? ? ? ? ? ? ? ? ?// 打开开关
? ? ?typename = typename std::enable_if<
? ? ? ? ? ? ? ? ? ?// 判断是否是继承关系
? ? ? ? ? ? ? ? ? !std::is_base_of<Person,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 去掉引用,const,volatile等修饰词
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? typename std::decay<T>::type
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?>::value
? ? ? ? ? ? ? ? >::type
? ?>
? ?explicit Person(T&& n);
?};
??
?// C++14版本
?class Person {
?public:
? ?template<
? ? ?teypename T,
? ? ? ? ? ? ? ? ?// 打开开关
? ? ?typename = typename std::enable_if_t<
? ? ? ? ? ? ? ? ? ?// 判断是否是继承关系
? ? ? ? ? ? ? ? ? !std::is_base_of<Person,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 去掉引用,const,volatile等修饰词
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? typename std::decay_t<T>
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?>::value
? ? ? ? ? ? ? ? >
? ?>
? ?explicit Person(T&& n);
?};
万能引用形参在性能方面具有优势,但是在易用性方面存在劣势,需要根据实际情况进行权衡。 条款二十八:理解引用折叠什么是引用折叠? 其实就是出现引用的引用的时候的一种处理方式,将引用的引用变成单个引用。 引用折叠的判断规则? 如果引用的引用这里两个引用中有任一引用为左值引用,结果就是左值引用,否则为右值引用。 引用折叠发生的语境
注:万能引用就是在类型推导过程中会区分左值和右值,以及会发生引用折叠的语境中的右值引用。 条款二十九:假定移动操作不存在,成本高,未使用不存在? 并不是所有的类型都支持移动操作,这样移动操作就变成了复制操作。 成本高? 对于std::array,15个字符以内的std::string等对象,由于数据直接内置了,移动操作依然需要复制数据,并不能更快。 未使用? 移动本可以发生的语境下,要求移动操作不可以抛出异常,但是该函数未加上noexcept声明,导致编译器还是选择了复制操作。 对于以上三种情况来看,只有对于类型或移动语义的支持情况已知的代码,才适合使用移动操作。 条款三十:熟悉完美转发的失败类型什么是完美转发失败? 对于基础函数f和完美转发函数fwd,如果f(expressin)和fwd(expression)执行了不同的操作或者fwd推导失败的情形。 完美转发失败的类型
?void f(const std::vector<int>& v);
??
?template<typename... Ts>
?void fwd(Ts&&... params) {
? ?f(std::forward<Ts>(param)...);
?}
??
?// 因为{}不是一个具体类型,所以编译器会被禁止在fwd的调用过程中从表达式中推导类型
?fwd({1,2,3}); // 错误,无法编译
??
?// 因为auto可以进行推导类型,所以如下修改
?auto i1 = {1,2,3};
?fwd(i1); // 通过编译
0和NULL传递时一般会被推导成整形,应该使用nullptr。
?// 对于类中的整形static const变量可以只声明不定义
?class Widget {
? public:
? ?static const std::size_t MinVals = 28; // 给出了MinVals的声明
? ...
?};
?... // 未给出MinVals的定义
??
?void f(std::size_t val);
??
?f(Widget::MinVals); // 没问题,当f(28)处理
?// 引用当指针处理的,需要有某块内存去指向,而这里没有
?fwd(Widget::MinVals); // 错误,应该无法链接
对于函数名称作为参数时,其实就是个函数指针,并不带参数类型等函数签名,这样完美转发就无从判断具体是哪种重载函数的指针。
?struct IPv4Header {
? ?std::uint32_t version:4,
? ? ? ? ? ? ? ? ?IHL:4,
? ? ? ? ? ? ? ? ?DSCP:6,
? ? ? ? ? ? ? ? ?ECN:2,
? ? ? ? ? ? ? ? ?totalLength:16;
?};
??
?void f(sd::size_t sz);
??
?IPv4Header h;
?...
?f(h.totalLength); // 没问题
?// 引用无法绑定到位域
?fwd(h.totalLength); // 错误
??
?//修改成以下代码,复制位域值就可以
?auto length = static_cast<std::uint16_t>(h.totalLength);
?fwd(length); // 转发该副本
|
|
|
| C++知识库 最新文章 |
| 【C++】友元、嵌套类、异常、RTTI、类型转换 |
| 通讯录的思路与实现(C语言) |
| C++PrimerPlus 第七章 函数-C++的编程模块( |
| Problem C: 算法9-9~9-12:平衡二叉树的基本 |
| MSVC C++ UTF-8编程 |
| C++进阶 多态原理 |
| 简单string类c++实现 |
| 我的年度总结 |
| 【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
| c语言常见错误合集 |
|
|
| 上一篇文章 下一篇文章 查看所有文章 |
|
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
| 360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年12日历 | -2025/12/16 0:41:43- |
|
| 网站联系: qq:121756557 email:121756557@qq.com IT数码 |