对C++11中的`移动语义`与`右值引用`的介绍与讨论 (6)

由于string& operator=(string that)是值类型参数, 所以在调用发生时, 参数的传递会使用x + y先去初始化that, 你可以理解为string that(x)这种. 由于x + y是一个临时量右值, 所以that的初始化使用的是string(string&& that)这个构造函数, 在这个构造函数内部, that偷掉了x + y内部持有的数据, 并没有发生数据拷贝.

在执行std::swap(data, that.data)的过程中, b持有的数据与that持有的数据相互交换. 至此, x + y原持有的数据经过二次转手, 来到了b的手上. 而b原持有的数据, 则交换给了that

在return *this执行之后, that由于函数退栈, 被析构. that中持有的数据(其实是原b持有的数据)被析构函数安全释放

总结起来: b = x + y内部, 经过两次转手, 将x + y持有的数据转交给了b, 而b原持有的数据被完全的析构掉了.

第六行和第五行类似.

至此, 你可算是基本明白了C++11中的移动语义. 现在, 请回头再看copy-and-swap小节的末尾, 你就会明白, 为什么copy-and-swap + rule of three + C++11 == rule of four and a half了

移动语义, 值类别, 右值引用, 将亡值等新概念的深入讨论

我们在这里, 再对移动语义, 右值引用等内容做一些补充

概览

移动语义允许一个对象, 在一些受限的上下文中, 去夺取另外一个同类型对象的内部资源. 这有两个点:

它将C++03标准中, 代价昂贵的拷贝操作进行了优化. 但如果一个类类型, 内部并不掌管任何外部资源的话(无论是直接掌管, 还是由成员对象间接掌管), 移动语义是没有任何卵用的: 它实质上就是拷贝! 也就是说, 在这种情况下, 移动和拷贝, 指的是同一件事. 比如下面这个POD类:

class cannot_benefit_from_move_semantics { int a; // 移动一个int, 其实就是拷贝 float b; // 移动一个float, 其实也是拷贝 double c; // 移动一个double, 其实还是拷贝 char d[64]; // 移动一个字节数组, 其实还他妈是拷贝 // ... };

移动语义的引入可以让程序员写出这样一种类: 它的对象仅能移动, 而不能被拷贝. 这种对象中或许掌管着诸如锁, 文件句柄, 智能指针这样的全局或局部单例资源.

移动的本质是什么?

C++98中的标准库提供了一个智能指针模板类, 其语义是唯一性的指向一个对象. 即是大家熟悉的std::auto_ptr<T>. 如果你不熟悉auto_ptr, 可以将它理解为一个"保证new出来的对象一定会妥善析构(甚至在有异常抛出的场合里)而不需要程序员手动delete"的小工具, 比如下面这种用法:

{ std::auto_ptr<Shape> a (new Triangle); } // <- 当代码执行流程跳出这个作用域的时候, 对象a就会被自动析构

其实, auto_ptr中值得称道的就是它的"拷贝"操作, 下面用一个简略的ASCII图来说明:

auto_ptr<Shape> a(new Triangle); // 智能指针a指向一个新创建和Triangle对象 +---------------+ | triangle data | +---------------+ ^ | | | +-----|---+ | +-|-+ | a | p | | | | | +---+ | +---------+ auto_ptr<Shape> b(a); // 使用a去初始化另外一个智能指针b, 其实a与b均指向了同一个Triangle对象 +---------------+ | triangle data | +---------------+ ^ | +----------------------+ | +---------+ +-----|---+ | +---+ | | +-|-+ | a | p | | | b | p | | | | | +---+ | | +---+ | +---------+ +---------+

关键点在于, 用a去初始化b的时候, 在智能指针对象这一层, 确实新建了一个智能指针对象, 也就是auto_ptr<Shape>类的实例. 但在内部, 并没有新建一个Triangle对象, 两个智能指针对象指向的是同一个Triangle对象. 这就是移动语义的最初发源, 所以, 正确理解移动语义需要理解下面两句话:

当我们讲, 将a移动到b的时候. a和b其实还是相互独立的两个实例, 各自在内存中占用着各自的空间.

移动语义中的移动, 其实说的是a将它"持有的资源"交给了b, 这种资源一般都是以指针形式指向的动态资源.

移动并不是说, 内存中的a对象本身改名叫b了, 并不是. a和b还是各自独立的两个对象, 分别有自己的内存. 这一点一定要正确理解.

auto_ptr之所以能实现这种功能, 其实是auto_ptr<T>的拷贝构造函数使用了如下的实现方式(就说这么个意思, 但并不是真的是这样写的代码):

auto_ptr(auto_ptr & source) { p = source.p; source.p = 0; } 对移动语义的错误理解导致的误用

auto_ptr时至今日已经被抛弃, 其缘由就是, 它的行为看起来让程序员以为是"拷贝", 但实际上是"移动". 比如下面的例子:

auto_ptr<Shape> a(new Triangle); auto_ptr<Shape> b(a); // a的资源, 也就是实际的"Triangle对象", 已经交由b了 double area = a->area(); // 完犊子

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzspdp.html