您的位置:首页 > 编程语言 > C语言/C++

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

2015-11-09 23:57 155 查看
按值传递的意义是什么?

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

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

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

所以我们需要寻找一个避免不必要拷贝的方法,即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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: