C++11新特性之 Move semantics(移动语义)

按值传递的意义是什么?

当一个函数的参数按值传递时,这就会进行拷贝。当然,编译器懂得如何去拷贝。

而对于我们自定义的类型,我们也许需要提供拷贝构造函数。

但是不得不说,拷贝的代价是昂贵的。

所以我们需要寻找一个避免不必要拷贝的方法,即C++11提供的移动语义。

上一篇博客中有一个句话用到了:

#include <iostream>

void f(int& i) { std::cout << "lvalue ref: " << i << "\n"; }
void f(int&& i) { std::cout << "rvalue ref: " << i << "\n"; }

int main()
{
    int i = 77;
    f(i);    // lvalue ref called
    f(99);   // rvalue ref called

    f(std::move(i));  // 稍后介绍

    return 0;
}

实际上,右值引用注意用于创建移动构造函数和移动赋值运算。

移动构造函数类似于拷贝构造函数,把类的实例对象作为参数,并创建一个新的实例对象。

但是 移动构造函数可以避免内存的重新分配,因为我们知道右值引用提供了一个暂时的对象,而不是进行copy,所以我们可以进行移动。

换言之,在设计到关于临时对象时,右值引用和移动语义允许我们避免不必要的拷贝。我们不想拷贝将要消失的临时对象,所以这个临时对象的资源可以被我们用作于其他的对象。

右值就是典型的临时变量,并且他们可以被修改。如果我们知道一个函数的参数是一个右值,我们可以把它当做一个临时存储。这就意味着我们要移动而不是拷贝右值参数的内容。这就会节省很多的空间。

说多无语,看代码:

#include <iostream>
#include <algorithm>

class A
{
public:

    // Simple constructor that initializes the resource.
    explicit A(size_t length)
        : mLength(length), mData(new int[length])
    {
        std::cout << "A(size_t). length = "
        << mLength << "." << std::endl;
    }

    // Destructor.
    ~A()
    {
    std::cout << "~A(). length = " << mLength << ".";

    if (mData != NULL) {
            std::cout << " Deleting resource.";
        delete[] mData;  // Delete the resource.
    }

    std::cout << std::endl;
    }

    // Copy constructor.
    A(const A& other)
        : mLength(other.mLength), mData(new int[other.mLength])
    {
    std::cout << "A(const A&). length = "
        << other.mLength << ". Copying resource." << std::endl;

    std::copy(other.mData, other.mData + mLength, mData);
    }

    // Copy assignment operator.
    A& operator=(const A& other)
    {
    std::cout << "operator=(const A&). length = "
             << other.mLength << ". Copying resource." << std::endl;

    if (this != &other) {
        delete[] mData;  // Free the existing resource.
        mLength = other.mLength;
            mData = new int[mLength];
            std::copy(other.mData, other.mData + mLength, mData);
    }
    return *this;
    }

    // Move constructor.
    A(A&& other) : mData(NULL), mLength(0)
    {
        std::cout << "A(A&&). length = "
             << other.mLength << ". Moving resource.\n";

        // Copy the data pointer and its length from the
        // source object.
        mData = other.mData;
        mLength = other.mLength;

        // Release the data pointer from the source object so that
        // the destructor does not free the memory multiple times.
        other.mData = NULL;
        other.mLength = 0;
    }

    // Move assignment operator.
    A& operator=(A&& other)
    {
        std::cout << "operator=(A&&). length = "
             << other.mLength << "." << std::endl;

        if (this != &other) {
          // Free the existing resource.
          delete[] mData;

          // Copy the data pointer and its length from the
          // source object.
          mData = other.mData;
          mLength = other.mLength;

          // Release the data pointer from the source object so that
          // the destructor does not free the memory multiple times.
          other.mData = NULL;
          other.mLength = 0;
       }
       return *this;
    }

    // Retrieves the length of the data resource.
    size_t Length() const
    {
        return mLength;
    }

private:
    size_t mLength; // The length of the resource.
    int* mData;     // The resource.
};

移动构造函数

语法:

A(A&& other) noexcept    // C++11 - specifying non-exception throwing functions
{
  mData =  other.mData;  // shallow copy or referential copy
  other.mData = nullptr;
}

最主要的是没有用到新的资源,是移动而不是拷贝。

假设一个地址指向了一个有一百万个int元素的数组,使用move构造函数,我们没有创造什么,所以代价很低。

// Move constructor.
A(A&& other) : mData(NULL), mLength(0)
{
    // Copy the data pointer and its length from the
    // source object.
    mData = other.mData;
    mLength = other.mLength;

    // Release the data pointer from the source object so that
    // the destructor does not free the memory multiple times.
    other.mData = NULL;
    other.mLength = 0;
}

移动比拷贝更快!!!

移动赋值运算符

语法:

A& operator=(A&& other) noexcept
{
  mData =  other.mData;
  other.mData = nullptr;
  return *this;
}

工作流程这样的:Google上这么说的:

Release any resources that *this currently owns.

Pilfer other’s resource.

Set other to a default state.

Return *this.

// Move assignment operator.
A& operator=(A&& other)
{
    std::cout << "operator=(A&&). length = "
             << other.mLength << "." << std::endl;

    if (this != &other) {
      // Free the existing resource.
      delete[] mData;

      // Copy the data pointer and its length from the
      // source object.
      mData = other.mData;
      mLength = other.mLength;

      // Release the data pointer from the source object so that
      // the destructor does not free the memory multiple times.
      other.mData = NULL;
      other.mLength = 0;
   }
   return *this;
}

让我们看几个move带来的好处吧!

vector众所周知,C++11后对vector也进行了一些优化。例如vector::push_back()被定义为了两种版本的重载,一个是cosnt T&左值作为参数,一个是T&&右值作为参数。例如下面的代码:

std::vector<A> v;
v.push_back(A(25));
v.push_back(A(75));

上面两个push_back()都会调用push_back(T&&)版本,因为他们的参数为右值。这样提高了效率。

而 当参数为左值的时候,会调用push_back(const T&) 。

#include <vector>

int main()
{
    std::vector<A> v;
    A aObj(25);       // lvalue
    v.push_back(aObj);  // push_back(const T&)
}

但事实我们可以使用 static_cast进行强制:

// calls push_back(T&&)
v.push_back(static_cast<A&&>(aObj));

我们可以使用std::move完成上面的任务:

v.push_back(std::move(aObj));  //calls push_back(T&&)

似乎push_back(T&&)永远是最佳选择,但是一定要记住:

push_back(T&&) 使得参数为空。如果我们想要保留参数的值,我们这个时候需要使用拷贝,而不是移动。

最后写一个例子,看看如何使用move来交换两个对象:

#include <iostream>
using namespace std;

class A
{
  public:
    // constructor
    explicit A(size_t length)
        : mLength(length), mData(new int[length]) {}

    // move constructor
    A(A&& other)
    {
      mData = other.mData;
      mLength = other.mLength;
      other.mData = nullptr;
      other.mLength = 0;
    }

    // move assignment
    A& operator=(A&& other) noexcept
    {
      mData =  other.mData;
      mLength = other.mLength;
      other.mData = nullptr;
      other.mLength = 0;
      return *this;
    }

    size_t getLength() { return mLength; }

    void swap(A& other)
    {
      A temp = move(other);
      other = move(*this);
      *this = move(temp);
    }

    int* get_mData() { return mData; }

  private:
    int *mData;
    size_t mLength;
};

int main()
{
  A a(11), b(22);
  cout << a.getLength() << ‘ ‘ << b.getLength() << endl;
  cout << a.get_mData() << ‘ ‘ << b.get_mData() << endl;
  swap(a,b);
  cout << a.getLength() << ‘ ‘ << b.getLength() << endl;
  cout << a.get_mData() << ‘ ‘ << b.get_mData() << endl;
  return 0;
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-06 02:08:47

C++11新特性之 Move semantics(移动语义)的相关文章

C++11 新特性(2) 移动语义

C++11支持移动语义. 一:为什么需要移动语义和什么是移动语义 我们先来看看C++11之前的复制过程.假设有下列代码: vector<string> v1(1000000);//v1存放着100W个string,假设每个string长度为1000 vector<string> v2(v1);//使用v1初始化v2 vector和string类都使用动态内存分配,因此他们必须定义使用他们自己的new版本的复制构造函数. 复制构造函数vector<string>将使用ne

C++11新特性之move与forward

1.move:返回arg的右值引用. template <class T> typename remove_reference<T>::type&& move (T&& arg) noexcept; 示例: class MemoryBlock { public: explicit MemoryBlock(size_t length) : _length(length), _data(new int[length]) { cout << &

C++11新特性Move Semantic及实现的基础xvalue

 知识点: 最小公倍数(a,b)=a*b/最大公约数(a,b) Party Description The CEO of ACM (Association of Cryptographic Mavericks) organization has invited all of his teams to the annual all-hands meeting, being a very disciplined person, the CEO decided to give a money aw

C++11——新特性总结

前言: 开学过去一个半月了,说来十分惭愧,由于和女友最后还是分开了,导致这段时间一直在沉沦,每天晚上回去打打lol或者cs,就睡觉,基本上把我自己定下的自学目标给抛弃了.好在这段时间里还是凭借以前的基础投了不少岗位,也笔试了不少公司,基本都通过了笔试.第一次面试是网易,结果在最后一轮的技术面上挂了下来.其实回想起来,当时问的问题我其实之前都有仔细的专研过,只不过时间太久忘了罢了.这也要怪我自己准备不够充分.之前腾讯的笔试,我其实感觉自己是做砸了的,不过没想到还是得到了面试机会,就在两天之后的下午

C++11新特性:右值引用和转移构造函数

问题背景 [cpp] view plaincopy #include <iostream> using namespace std; vector<int> doubleValues (const vector<int>& v) { vector<int> new_values( v.size() ); for (auto itr = new_values.begin(), end_itr = new_values.end(); itr != end

9秒学院学C++11新特性

9秒学院C++11新特性学习笔记 分类: C/C++ 最近学习了C++11的新特性,将学习内容整理下来以巩固记忆,C++11的新特性,可以分为两部分,第一部分是C++11核心语言的特性,第二部分是STL标准库的新特性.学习C++11主要参考了wiki上的一篇文章,在介绍右值引用的时候还参考了MSDN上一篇文章,由于这两篇文章写的时间比较早,和实际有些出入,我的开发环境是win8,vs2012,很多C++11特性还没支持,所以只整理了vs2012已经支持了的特性. 第一部分:核心语言的特性 一.

Oracle 12.2新特性----在线move表

Oracle12.2版本之前,对表做move操作时会对表加exclusive锁,表上无法执行DML操作.虽然move操作有ONLINE子句,但只适用于IOT表,不适用于堆表.这就意味着在对表做move操作时,无法执行任何DML操作,如果对关键表做move操作时只能停业务来完成.到了Oracle12.2版本,推出了一个新特性----在线move表,对于普通堆表可以在move过程中执行DML操作. 下面以11.2.0.4和12.2.0.1这两个版本为对比,观察这一新特性. 1.11.2.0.4版本的

逐个使用C++11新特性

C++11 auto & decltype auto:根据变量初始值来推导变量类型,并用右值初始化变量. decltype:从表达式推导出类型,并将变量定义为该类型,但不用表达式的值初始化该变量. 这里要注意下:decltype(i)--是i的类型,而decltype((i))就是引用了.就像上面例子中的x 就是int &x; 右值引用 新标准在拷贝构造函数.拷贝赋值运算符和析构函数之外,还增加了移动构造函数和移动赋值运算符,而这二位就需要右值引用的支持. 1. 延长将亡值的生命. 1 /

C++11 新特性之 tuple

我们在C++中都用过pair.pair是一种模板类型,其中包含两个数据值,两个数据的类型可以不同.pair可以使用make_pair构造 pair<int, string> p = make_pair(1, "a1"); 如果传入的参数为多个,那么就需要嵌套pair,如下代码 #include <iostream> #include <map> using namespace std; int main() { //<int, string,