翻译「C++ Rvalue References Explained」C++右值引用详解 Part4:强制Move语义

本文为第四部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/4220233.html

强制Move语义

众所周知,正如C++标准的第一修正案所陈述:“委员会不会建立任何试图绊住C++程序员的脚的规则。(The committee shall make no rule that prevents C++ programmers from shooting themselves in the foot.”,正经来说,就是当面临给予程序员更多控制还是减少他们粗心大意机会的选择时,C++更倾向于及时可能导致犯错,但是依然给予更多控制。正是基于这种精神,C++11允许你使用Move语义而不仅仅局限于是右值,而是还有左值,这都给与你充分的决定权。一个好的例子就是标准库里面的swap方法。同以前一样,给出一个类X,基于它我们可以重载拷贝构造函数和拷贝赋值操作符来在右值上面实现Move语义。

template<class T>
void swap(T& a, T& b)
{
  T tmp(a);
  a = b;
  b = tmp;
} 

X a, b;
swap(a, b);

这里并没有右值。所以,在swap函数中的三行没有使用move语义。但是我们知道使用move语义是可行的:任何时候当一个变量作为源头出现在一个拷贝构造函数或者赋值语句的时候,那个变量将不会再被使用,或者仅仅被作为赋值的目标。

在C++11中,标准库中有一个叫做std::move的方法用来处理这种情况。这个函数只是将他的参数变成右值。因此,在C++11中,标准库函数swap将会像如下所示:

template<class T>
void swap(T& a, T& b)
{
  T tmp(std::move(a));
  a = std::move(b);
  b = std::move(tmp);
} 

X a, b;
swap(a, b);

现在所有在swap函数中的三行都使用了move语义。记住对于没有实现move语义的类型(也就是说,并没有为右值单独重载一个拷贝构造函数和赋值表达式的类型),新的swap行为表现同旧的版本是一致的。

std::move是一个非常简单的函数。不幸的是,尽管如此,我还不能将它的实现展现给你。我们将会在后面讨论它。

在任何能使用std::move的地方使用它。如上面的swap函数所示,它给我们带来如下好处:

  • 对于实现了move语义的类型而言,需要标准库算法和操作将会使用move语义,然后因此得到潜在的性能提升。一个重要的例子就是原地排序(inplace sorting):原地排序算法就是单纯的交换元素,其他什么都不做,然后现在交换的过程将会在提供了move语义的类型中,充分利用到move语义提供的优势。
  • 标准模板库(STL)经常需要有些类型提供拷贝能力(copyability)。例如可以被用做容器元素的类型。通过严格的观察,事实证明,在很多情况下,移动能力(moveability)就足够了。因此,我们现在可以在某些以前并不被允许的地方使用可移动的而不是可复制的类型了(譬如unique_pointer)。举例来说,这些类型现在可以被使用做标准模板库的容器类型了。

既然我们知道了std::move,我就就需要知道为什么实现一个基于右值引用的拷贝赋值表达式的重载,如同前面我所展示的,依然是有些问题的。考虑一个简单的变量间的赋值,像这样:

a = b;

你期待在这里发生什么?你期待被a持有的对象被一份b的拷贝所替代,当然在整个交换过程中,你期待原来被a持有的对象会被析构。现在考虑这行代码:

a = std::move(b);

如果move语义被实现为一个简单的交换,那么这里的表现就将会是被a和b持有的对象将在a和b间交换。没有任何东西被析构。当然原来被a持有的对象将会最终被析构,也就是说,当b离开了该代码的范围时。当然,除非b成为move的对象,在这种情况下,原来被a持有的对象又再次得到了一次。因此,只有拷贝赋值表达式的实现被精心考虑过后,我们并不知道原来被a持有的对象何时将被析构。

所以在某种意义上,我们在这里将会陷入到不确定性的泥沼中:一个变量被分配后,但是原来被该变量持有的对象却还在其他位置。只有那个对象的析构函数并不会对外面有任何副作用的时候才是可行的。但是某些时候,析构函数确实会有这种副作用。一个例子就是在析构函数中释放一个锁。因此,析构函数中任何可能含有副作用的部分应该在拷贝赋值操作符的右值引用重载中清晰地表现出来:

X& X::operator=(X&& rhs)
{

  // 执行一次清理,要注意到那些在析构函数中可能产生副作用的地方。
  // 确保对象处于可析构的和可赋值的状态

  // Move语义,交换this和rhs的内容

  return *this;
}
时间: 2024-08-04 16:13:57

翻译「C++ Rvalue References Explained」C++右值引用详解 Part4:强制Move语义的相关文章

翻译「C++ Rvalue References Explained」C++右值引用详解 Part2:Move语义

本文为第二部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/4220233.html. Move语义 假设x是一个类,其含有一个指针或者某些资源的句柄(handle).写作m_pResource.由这个资源,我的意思是包括构造.克隆.析构都认真考虑在内的,一个绝佳的例子是std::vector.它可以在一个分配的内存数组中包含一个对象集合.接下来,从逻辑上,对于x的拷贝赋值操作符一般如下: X& X::operator=(X const & r

翻译「C++ Rvalue References Explained」C++右值引用详解 Part1:概述

本文系对「C++ Rvalue References Explained」 该文的翻译,原文作者:Thomas Becker. 该文较详细的解释了C++11右值引用的作用和出现的意义,也同时被Scott Meyers推荐,全文共分11个部分,我将利用业余时间,分别翻译. 受笔者水平所限,可能叙述会出现些许问题,还望多多指正. 部分名词为了保持含义和方便理解,并未翻译成中文,有的在括号内给出了常见的中文翻译. 目录 概述 Move语义 右值引用 强制Move语义 右值引用就是右值吗? Move语义

翻译「C++ Rvalue References Explained」C++右值引用详解 Part3:右值引用

本文为第三部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/4220233.html. 右值引用 如果x是任意类型,那么x&&则被称作一个对x的右值引用(rvalue reference).为了更好区分,原来的引用x&现在也被称作左值引用(lvalue reference). 一个右值引用是一种同原始引用x&的行为非常类似的类型,但是有一些特例.最重要的一个就是当面临方法重载决议的时候,左值倾向于旧式的左值引用,而右值偏向于新的

翻译「C++ Rvalue References Explained」C++右值引用详解 Part5:右值引用就是右值吗?

本文为第五部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/cpp-rvalue-references-explained-introduction.html. 右值引用就是右值吗? 同之前一样,给出一个X类,让我们可以重载它的拷贝构造函数和拷贝赋值操作符来实现move语义.现在做如下考虑: void foo(X&& x) { X anotherX = x; // ... } 一个有趣的问题就是,在foo函数内,哪一个x的拷贝构造函数重载将会被

翻译「C++ Rvalue References Explained」C++右值引用详解 Part7:Perfect Forwarding(完美转发):问题

本文为第七部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/cpp-rvalue-references-explained-introduction.html. Perfect Forwarding(完美转发):问题 Move语义背后右值引用用来解决的另一个问题是完美转发问题.考虑下面这样简单的工厂函数: template<typename T, typename Arg> shared_ptr<T> factory(Arg arg)

Linux 小知识翻译 - 「协议(protocol)」

对于理解服务器和网络来说,「协议」是不可缺少的概念. 「协议(protocol)」有「规则,规定」的意思. 实际上「协议」的函数很广,在通信领域,「协议」规定了「在通信时,什么样的情况下,以什么样的顺序,什么样的方式交互什么样的数据」. 抽象的去理解「协议」可能会比较困难,下面来举个例子. 通过Web以HTML方式交互时使用的协议是「HTTP」(Hyper Text Transfer Protocol).这个协议最重要的就是规定了服务器和客户端之间以HTML方式交互的规则. 比如,客户端连接上服

Linux 小知识翻译 - 「内核(kernel)」

上次介绍了Linus Torvalds, 这次介绍他开发的「内核」. 经常听人提到「Linux kernel」,但如果被问到「kernel究竟是什么?」的话,会出乎意料的觉得难以回答. 那么,kernel到底是什么呢?「kernel」翻译过来就是「核心」的意思,简单来说,「Linux的核心程序就是Linux kernel」. 其实严格来说,「Linux」就是指「Linux kernel」.(最近,「Linux发行版」简称为「Linux」的情况越来越多) 将Linux kernel解释成「系统核心

Linux 小知识翻译 - 「端口和端口号」

这次说说「端口」和「端口号」. 平时经常会听人说「打开了80号端口」,为了安全「不要打开多余的端口」等等.那么,这里的端口或者端口号是什么呢? 首先,「端口」是TCP或者UDP上使用的概念,经常被比喻成「窗口」.而且,端口号也被比喻成窗口编号. 举个例子,比如去邮局汇款时,并不是每个窗口都可以汇款的,只有特定的窗口才能办理汇款业务.所以,每个窗口都会编号. 端口号和上面的情况类似,比如,Web服务器通过HTTP接受通信的时候,是通过Web服务器的80号端口来通信的. 同样,SMPT使用25号端口

Linux 小知识翻译 - 「命令行的提示符」

这次,聊聊关于「命令行提示符」的相关内容. bash之类的Shell程序是操作Linux所不可缺少的东西.其中bash的提示符也有承担了很重要的作用. 「命令行提示符」的英文是「command prompt」,其中「prompt」有促使,推动的意思,根据这个意思,Shell中的「命令行提示符」就有促使,推动用户输入的意思. (下面的说明以bash为例,bash是Shell程序的一种) 目前的Linux发行版中,提示符一般都像下面这样: [[email protected] /etc]$ 上面这个