c++ 移动语义及使用(续)

Posted 0号程序员

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++ 移动语义及使用(续)相关的知识,希望对你有一定的参考价值。

接上文~

5 如果使用移动语义

前边我们讲了好多为什么使用移动语义,以及一些使用的例子,这里我们再具体展开说下。讲之前我还是要啰嗦下移动引用和move,右值引用的重要目的就是用来实现移动语义,move方法的目的是将左值强制转为右值,正好方便应用移动语义。 
所以可以简单的生成一下方法论,如果我们打算移动一个资源到另一个变量时,如果原有资源是是右值,我们可以直接使用右值引用接收即可,但是如果原有资源是左值,我们可能就需要用到move转换成右值。我们再从例子中深化下:

5.1 一些会搞错的点

我们明确一点,调用函数接收返回值时,c++11之前通过拷贝到接收对象,c++11之后通过移动到接收对象

Obj getObj(int v) {
  if (v < 1) {
    return Obj();
  }
  else {
    Obj o;
    o.ptr_[0] = 10;
    return o;
  }
}

Obj o1 = getObj();

还是这个例子,我们学完了移动语义,来看下,提三个问题

  1. 函数声明Obj要不要做下改动,Obj&& getObj(int),或者const Obj& getObj(int),再或者Obj& getObj(int)
  2. else的那个分支要不要用move,即return std::move(o);
  3. 调用getObj时,要不要Obj&& o = getObj(),或者const Obj& o = getObj(),再或者Obj& o = getObj();

是不是看完有点懵,是不是感觉自己好像没学过呢,哈哈。看到这里,大家可以直接写代码试试看,解答三个问题之前,先要明确的两点:

Obj o1;
Obj&& o2 = getObj();  // 1
Obj o3 = std::move(o1);    // 2

看上边代码,仅对这个“=”操作来说,1会不会执行移动构造函数,不会对吧,因为o2是getObj的引用,相同指向,不涉及新对象创建。我们可以类比左值引用。2会不会执行移动构造函数,会的,因为o3是一个新的需要创建的对象,这一点很对同学会搞懵掉,需要注意。

然后我们来解答问题:

  • 首先看第一个:a).如果返回值是Obj&,if分支编译不过,因为左值引用不能指向右值, 即使编译过也千万注意,返回的对象不是栈上的,因为函数执行完后会释放掉,接收这块资源的对象指向地址是无效的,当然你如果返回的不是栈上的变量倒是可以,生命周期一直存在的。b).如果是Obj&&,else分支编译不过,不过o可以使用move转成右值。不过同样是返回栈上的对象,接收的时候也会被释放掉 c).同a。所以abc三种写法一般情况下不会这么写,除非声明周期一直存在,而不是返回栈上变量。
  • 然后看第二个,返回值我们要不要用move, 答案是没必要,使用std::move对于移动行为没有帮助,有时反而会影响返回值优化。
Obj getObj(int v) {
    Obj o;
    o.ptr_[0] = 10;
    return std::move(o);
}

Obj oo = getObj(1);

我们看这个例子,各个函数执行情况是:

我们把return std::move(o);改成return o;结果是这样的:

结果我写了move反而会影响效率,真是头疼。哈哈~

  • 继续看第三个,Obj& o = getObj()这个语法错误,或者const Obj& o = getObj()和Obj&& o = getObj()这两个对效率来讲少一次移动,这样写针对返回的prvalue来说是延长了生命周期,和接收对象保持一样的生命周期。有时候编译器也会做优化即使不这么写也没有移动,不过这样写确实效率还是提高了。

5.2 我们怎样使用移动语义

  1. 我们上边的那个例子其实就是,因为我们要返回临时对象, 如果这个对象是stl的,因为几乎所有stl的类都有移动构造函数,但是如果是我们自定义的类,我们就需要加一个移动构造函数了。
  2. 参数使用右值引用使用移动语义,但是一般我们需要写重载函数也要来接收左值的。比如说我们上边的那个push_back函数。还有一些我们使用stl或者其他库,我们也尽量使用那个右值引用参数那个函数,这样如果我们是个左值,就需要用到move了。
  3. 还有就是用右值引用接收函数返回值。

我目前能想到大概也是这样几种场景,欢迎大家补充

5.3 自己写一个可以移动的类

我们上边也说了如果要一个类可以移动,那么我们要实现他的移动构造函数,但是还是有一些注意点。C++中五种特殊的称为拷贝控制成员的成员函数来控制对象拷贝,移动,赋值和销毁。分别是拷贝构造函数,拷贝赋值符,移动构造函数,移动赋值符和析构函数。以及比较著名的三五法则,大家也可以去看下,这里不是我们的重点,我们这里只讨论和移动相关的。

  1. 如果我们拷贝控制成员函数 均不写,编译器为我们实现默认版本。
  2. 如果我们有析构函数或者拷贝构造函数或者拷贝赋值符,那么编译器不会为我们生成移动构造函数及移动赋值符。(只有没有定义任何一个拷贝控制成员且成员可以移动,编译器才会生成默认移动构造函数和移动赋值符)
class Obj {
public:
  Obj() : ptr_(new int[10]{0}) {
    std::cout << "Obj()" << std::endl;
  }
  ~Obj() {
    std::cout << "~Obj()" << std::endl;
    if (ptr_ != nullptr) {
        delete []ptr_;
    }
  }

  int* ptr_;
};

如果只是这么写,那么在Obj o = getObj(10);函数执行析构时会报错。因为如果没有移动构造函数,在执行getObj时会使用拷贝构造函数,而默认的拷贝构造函数是一个浅拷贝,对象析构时两个对象的指针指向相同资源,被析构两次执行报错。

  1. 如果写了移动构造函数或者移动赋值符,编译器也不会生成拷贝构造函数,拷贝赋值符及析构函数。

所以给予这些原则,我们一般来说,拷贝控制成员看成一个整体,写一个就得写全部,不过也不是必须的,但是你的头脑里也要过一下这些原则。

  • 比如说你只进行拷贝操作或者拷贝和移动实现基本一致,那可以只写拷贝相关的。
  • 或者说你只写拷贝构造函数和移动构造函数,而不想写=操作符,这样也就不会去用赋值操作嘛

等等一些情况吧 
我们这里来看一个完整的例子,还是以obj为例:

class Obj {
public:
  Obj() : ptr_(new int[10]{0}) {}
  
  ~Obj() {
    if (ptr_ != nullptr) {
        delete []ptr_;
    }
  }

  Obj(const Obj& o1) {
    ptr_ = new int[10]{0};
    for (int i= 0; i < 10; ++i) {
        ptr_[i] = o1.ptr_[i];
    }
  }

  Obj(Obj&& o1) noexcept {
    ptr_ = o1.ptr_;
    o1.ptr_ = nullptr;
  }

  Obj& operator=(Obj o) noexcept {
    o.swap(*this);
    return *this;
  }

  void swap(Obj& o) noexcept {
    using std::swap;
    swap(ptr_, o.ptr_);
  }

  int* ptr_;
};

void swap(Obj& lhs, Obj& rhs) noexcept
{
  lhs.swap(rhs);
}

我们看下那些地方有改动,首先移动构造函数多了一个noexcept表示不抛出异常,这里是告诉编译器这个函数不会抛出异常,大家写移动构造函数最好也不要抛出异常,因为本来只是一个资源的转移,实在是没有理由抛出异常。然后我们只写了一个“=”运算符,也不抛出异常,使用swap可以做到,一个成员函数swap用来自己使用,一个全局swap函数,用来给其他拥有这个Obj对象的类的赋值时使用。

引用

  1. 深入应用c++11
  2. c++ primer
  3. C++0x漫谈》系列之:右值引用
  4. https://github.com/adah1972/geek_time_cpp


以上是关于c++ 移动语义及使用(续)的主要内容,如果未能解决你的问题,请参考以下文章

❥关于C++之右值引用&移动语义┇移动构造&移动复制

续:纠正:ubuntu7.04可以安装,而且完美的安装 ! for《Oracle-10.2.0.1,打补丁10.2.0.5:在 debian 版本4不含4以上,及 ubuntu 7.04不含(代码片段

是否可以在 C++ 中将返回值移动语义与受保护的复制构造函数一起使用?

c++的左值(lvalue),右值(rvalue),移动语义(move),完美转发(forward)

一文入魂:再也不用担心我不懂C++移动语义了!

一文入魂:妈妈再也不用担心我不懂C++移动语义了!