《深入浅出地带你认识c++11中的移动语义》

导语:

        搜索c++的移动语义,涌入屏幕之中的便是std::move()、右值、左值,更甚者还要捎上一些晦涩难懂的完美转发和通用引用。但移动语义的强大从不来自于复杂,而是背后优雅的设计思想。本文将用最简单的方式解释清除c++11中的移动语义到底“移动”了什么。

std::move移动了什么?

        Nothing。事实就是std::move()不会移动任何东西。

        首先感性上认识:一个物体从A移动到B意味着两件事,【原来位置A处的消失】和【新增位置B处的出现】,但在计算机的世界中则不然。

       现在我们考虑一个常见的情况。下列的代码会导致几次析构发生呢?时常会有同学告诉我“只有一次,因为A被std::move()直接移动到了B中,避免了拷贝的发生,提高了程序性能......”。但我们已经说过:“std::move()不会移动任何东西”。

class Box{...}

Box A;
Box B = std::move(A);

        将A移动到B的过程在计算机的世界中只会有【新增位置B处的出现】,而不会有【原来位置A处的消失】,理解这一点至关重要!换而言之,上述代码中析构会发生两次,因为原来的A仍然存在,仍然需要调用析构函数才能析构,而新增的B则会调用另一次析构函数。

移动了个寂寞?!

        相信此时不少同学会有疑问。这样似乎就是简单地将A拷贝到B中,那移动的意义在哪?以前了解过移动语义的同学一定听说过“所有权”这个概念。拷贝的意义在于保护A对其资源的所有权,而移动则是强迫A放弃对其资源的所有权。因此在拷贝构造函数中会有const加以限制,而移动构造函数中则使用没有限制的右值。

//Copy
Box::Box(const Box & other)

//Move
Box::Box(Box && other)

        此时移动语义的巧妙之处便出现了,A放弃了资源的所有权意味我们可以自由访问A的资源,比如将指针置为空、提前释放资源,或者“移动”过来用于构造B。如何移动?当A所持有的资源只有基本类型诸如int,double...时,拷贝A的资源就是在移动A的资源,因此移动和拷贝的语义在此时没有区别。但如果A所持有的资源存在指针p,而指针p又指向一个大小足足有1G的数组,那么使用拷贝语义则难以胜任,因为拷贝指针意味复杂的指针管理;而使用移动语义则可以轻松地偷走这把开启数组宝箱的钥匙p,同时将原始指针置为nullptr以了无后顾之忧。

        总之移动语义不会移动任何东西,更形象的说法可以是“重定向对象所持的资源”,但一定不应该是用对象A直接替代对象B。移动语义仍然需要创建新的对象,仍然需要析构原来的对象,就像抄学霸的暑假作业一样,温顺的小拷会准备一本新的,然后一题一题慢慢抄;狠人小移则是直接把学霸暑假作业的名字改成自己的,但无论再狠也没办法说自己就是学霸本人(¬︿̫̿¬☆)。所以再也不要考虑“在代码某处创建好一个对象后,使用各种完美转发和移动语义最后鬼使神差般的把这个对象送到一个vector末尾以提高性能”这种离奇的操作了......

猜你喜欢

转载自blog.csdn.net/fish_toucher/article/details/133173246