C++ 11引入了移动语义以提高对象“复制”的效率,这种复制效率对于容器而言至关重要。清晰明了地向学生解释移动语义、右值引用并不是一件容易的事,为此,我们设计了一个简单明了的示例,化繁为简地说理,试图解决这一问题。

本文引用自作者编写的下述图书; 本文允许以个人学习、教学等目的引用、讲授或转载,但需要注明原作者"海洋饼干叔
叔";本文不允许以纸质及电子出版为目的进行抄摘或改编。
1.《Python编程基础及应用》,陈波,刘慧君,高等教育出版社。免费授课视频 Python编程基础及应用
2.《Python编程基础及应用实验教程》, 陈波,熊心志,张全和,刘慧君,赵恒军,高等教育出版社Python编程基础及应用实验教程
3. 《简明C及C++语言教程》,陈波,待出版书稿。免费授课视频

19.9 移动语义*

19.9.1 对象复制的编译优化

编译器会穷尽所能进行代码优化,避免不必要的对象复制行为。在下述代码中,我们定义了一个Message类,其中包含一个动态分配的缓冲区buffer用于存储真正的消息文本。此外,Message类还定义了拷贝构造函数以及自定义operator=()操作符函数。理论上,一个Message对象可以十分“巨大”,对其进行复制费时费力。

//Project - MessageCopy
#include <iostream>
#include <cstring>
using namespace std;class Message {char* buffer = nullptr;
public:int id = 0;Message(){ cout << "Constructor, id = " << id << endl; }Message(int id, const char* text){cout << "Constructor, id = " << id << endl;this->id = id;buffer = new char[strlen(text)+1];strcpy(buffer,text);}Message(const Message& r){cout << "Copy Constructor, from " << r.id << " to " << id << endl;id = r.id;if (buffer) delete [] buffer;buffer = new char[strlen(r.buffer)+1];strcpy(buffer,r.buffer);}Message& operator=(const Message& r){cout << "operator=(), from " << r.id << " to " << id << endl;id = r.id;if (buffer) delete [] buffer;buffer = new char[strlen(r.buffer)+1];strcpy(buffer,r.buffer);return *this;}const char* content() const { return buffer; }~Message(){cout << "Destructor, id = " << id << endl;if (buffer) delete[] buffer;}
};Message fetchMessage(){Message m(1,"Washington, this is pearl harbour, we are under japanese attack!");return m;
}int main() {Message s = fetchMessage();cout << "Message " << s.id << ": " << s.content() << endl;return 0;
}

上述代码的执行结果为:

Constructor, id = 1
Message 1: Washington, this is pearl harbour, we are under japanese attack!
Destructor, id = 1

第44 ~ 47行:fetchMessage()函数构造“局部”对象m,然后返回。

第50行:main()函数的“局部”对象s接收fetchMessage()的返回对象。

逻辑上,上述代码至少存在两个Message对象,分别是main()函数内的s以及fetchMessage()函数里的m。多数读者会推导出如下的代码执行序列:m被构造并返回;返回的m作为参数参与s的拷贝构造;m被析构;s在main()函数返回时被析构。但作者计算机上的执行结果不支持上述推导,整个程序的生命周期内,只有编号为1的对象被构造及析构,整个程序事实上只生成了一个Message对象!

显然,这是编译器代码优化的结果。编译器认为先构造一个临时对象m再复制给s是没有必要的,它选择绕过中间对象的m,直接构造s:在fetchMessage()函数内对对象m进行的操作,事实上发生在外部的s对象上。从程序结果上看,编译器做得很好,省时省力且没有“误解”程序员的本意。

但编译器还没有厉害到可以完美地避免一切不必要的对象复制的程度。将上述代码的main()函数稍作调整:在第50行先构造对象s,然后再用s接受fetchMessage()返回对象的赋值。

int main() {Message s;s = fetchMessage();cout << "Message " << s.id << ": " << s.content() << endl;return 0;
}

调整后的代码在作者的计算机上获得了如下的执行结果:

Constructor, id = 0
Constructor, id = 1
operator=(), from 1 to 0
Destructor, id = 1
Message 1: Washington, this is pearl harbour, we are under japanese attack!
Destructor, id = 1

根据程序执行结果,我们可以逐行反推代码的执行序列:1).编号为0的对象s被构造;2).编号为1的对象m被构造;3).由于s已存在,返回对象m通过s的operator=()操作符函数复制给s;4).对象m析构;5).打印s的内容;6).对象s析构,由于s由m复制而来,所以执行结果第6行显示的编号为1。

“不必要”的对象复制发生在第3步。在operator=()操作符函数里,s对象分配了新的缓冲区buffer,然后一个字节又一个字节地从m对象复制缓冲区内容。考虑到临时对象m将很快被销毁,如果直接将m对象的缓冲区“偷”走,直接“挪”给s对象,将显著提高程序的执行效率。【C++ 11】引入了移动语义(move semantics)来解决这个问题。

19.9.2 右值引用

int a = 69;
a = a + 3;

C++标准引入了术语左值(lvalue)和右值(rvalue)来区分两种不同类型的对象。上述代码中的a具有确定的内存地址,它可以被赋值,我们称a为一个左值对象。在机器语言层面,表达式a+3的计算通常是借助于CPU寄存器来完成的,然后再从寄存器复制到对象a的内存。这个位于寄存器的临时对象没有确定的内存地址且“用完即弃”,我们称该对象为一个右值对象。

C++ 11的移动语义 - 清晰的示例及浅显的说理相关推荐

  1. PKCS#11 用户级加密应用程序示例

    转自 :http://docs.oracle.com/cd/E19253-01/819-7056/auto117/index.html 本节包含以下示例: 消息摘要示例 对称加密示例 签名和检验示例 ...

  2. [1-1 main ]Python主函数及其示例:理解__main_

    Python主函数及其示例:理解__main_ 在深入研究Python编码之前,我们熟悉了Python的主要功能及其重要性. 考虑以下代码 def main():print "hello w ...

  3. SUSE Linux Enterprise Server 11 64T 安装(带清晰视频)

    SUSE Linux Enterprise Server 11 64T 安装实录 安装本软件需要支持64位指令的CPU,否则会出现以上提示画面,如何判断自己的机器是否支持64位呢,安装CPUZ测试软件 ...

  4. C++11:move移动语义

    前言 我们知道移动语义是通过右值引用来匹配临时值,那么,普通的左值是否也能借助移动语义来优化性能呢,C++11为了解决这个问题,提供了std::move方法来将左值转换成右值. 正文 move是将对象 ...

  5. C++11 移动语义与右值引用

    1.移动语义 C++11 新标准中一个最主要的特性就是提供了移动而非拷贝对象的能力.如此做的好处就是,在某些情况下,对象拷贝后就立即被销毁了,此时如果移动而非拷贝对象会大幅提升程序性能.参考如下程序: ...

  6. 《JavaScript权威指南第7版》第11章 JavaScript标准库

    第11章 JavaScript标准库 11.1 Set和Map 11.1.1 Set类 11.1.2 Map类 11.1.3 WeakMap和WeakSet 11.2 类型数组和二进制数据 11.2. ...

  7. 新增11条新规约,阿里Java开发手册|黄山版,拥抱规范,远离伤害

    前言 阿里开发手册是阿里近万名开发同学集体智慧的结晶,以开发视角为中心,详细列举如何开发更加高效.更加容错.更加有协作性,力求知其然,更知其不然,结合正反例,让Java开发者能够提升协作效率.提高代码 ...

  8. html常用语义化元素和全局属性整理

    WWW www其实是World Wide Web 的缩写,它是一个由许多互相链接的超文本组成的系统,通过互联网访问, 在汉语中,www 被翻译成(web 万维网 全球广域网). <HTML> ...

  9. 《Java8实战》第11章 用 Optional 取代 null

    11.1 如何为缺失的值建模 public String getCarInsuranceName(Person person) { return person.getCar().getInsuranc ...

最新文章

  1. 31 天重构学习笔记29. 去除中间人对象
  2. 物联网的全球可用性如何带来新的商机?
  3. 图片导入ppt后模糊_PPT设计,找图也是一种能力
  4. 【LeetCode-面试算法经典-Java实现】【002-Add Two Numbers (单链表表示的两个数相加)】...
  5. 菜鸟CEO万霖:双11包裹量将继续增长 已建立更高效的物流枢纽
  6. 节约:我们不应忘记的美德
  7. OPENNLP——java的NLP工具
  8. FFmpeg之编译ffplay(十四)
  9. C.Fountains(Playrix Codescapes Cup (Codeforces Round #413, rated, Div. 1 + Div. 2)+线段树+RMQ)...
  10. 【数据结构】一张图让你读懂:树的高度、深度、层的区别
  11. 动态代理的中介出租房屋
  12. 鸿蒙与Android API对应关系
  13. 深圳绿色建筑数量和规模居全国榜首 建筑人才需求增加
  14. python一对一辅导教程:PyGeM Tutorials 解析 1
  15. HKC神盾显示器MG27Q - Nano IPS显示器的好选择
  16. 时间序列分析实验报告总结_时间序列实验报告.doc
  17. unity3d 非运行模式下执行脚本
  18. 52单片机与MH-Sensor-Series模块使用简单检测车轮转速
  19. 锐捷网络C++开发工程师校招一面面经
  20. Linux里vi介绍

热门文章

  1. RocketMQ 原理
  2. 评审人喜欢的国自然基金中英文摘要是怎样的(附模板)
  3. python 计时方法_Python计时器类| cancel()方法与示例
  4. kicad最小布线宽度默认是多少_PCB布线设计,提高有一套完整的方法
  5. yarn安装依赖超时
  6. 萌新如何用板绘画好原画?怎么选择数位板?零基础板绘入门干货篇
  7. js对文件和二进制操作的一些方法汇总
  8. Linux十大桌面环境
  9. PSN 港服申请退费流程(订阅退费流程差不多)
  10. 小程序下滑分页加载数据