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

C++10中的移动语义

2016-01-13 18:20 453 查看
首先,我们来看这样一个函数:

(T为一个对象类型)

T clone(const T& rhs)
{
T other = rhs;
return other;
}


这样的函数中,其实会调用两次拷贝构造函数。我们写一个实例看看:

class Example
{
public:
Example();
Example(const Example& other);
~Example();

Example clone();

public:
int count;
int* pNumber;
};


#include "move_semantic.h"
#include <iostream>

using namespace std;

Example::Example()
{
cout << "默认构造函数..." << '\n';
}

Example::Example(const Example& other)
{
count = other.count;
pNumber = new int[count];
// 深拷贝
for (int i = 0; i < count; i++)
pNumber[i] = other.pNumber[i];

cout << "拷贝构造函数..." << '\n';
}

Example::~Example()
{
delete pNumber;
cout << "这是析构函数..." << '\n';
}

Example Example::clone()
{
Example demo(*this);
return demo;
}

int main()
{
Example demo;
demo.count = 10;
demo.pNumber = new int[demo.count];
for (int i = 0; i < demo.count; i++)
demo.pNumber[i] = i + 1;

demo.clone();

return 0;
}


执行结果如下:



第一次默认拷贝构造函数的调用是在demo对象的初始化过程中;

两次拷贝构造函数实在clone函数的调用过程中:

clone函数中利用this对象初始化demo对象进行一个拷贝构造,然后返回demo对象。返回过程中会再次调用一次拷贝构造返回局部对象demo的一个拷贝。

如果利用C++11的移动语义(Move Semantics),则在clone函数返回的的时候,我们不是重新拷贝一个对象,而是将demo这个临时对象的所有权交给另外一个对象,这样避免了对象的拷贝,提高了效率。

对象的移动语义(Move Semantics)需要实现移动构造函数(move constructor)和移动赋值运算符(move assignment operator)。如果源对象是在复制或者赋值结束以后被销毁的临时对象,编译器会使用两种方法。移动构造函数和移动赋值运算符将成员变量从源对象复制/移动到新对象,然后将源对象的变量设置为空值。这样做实际上将内存的所有权从一个对象转移到另一个对象。这两种方法基本上只对成员变量进行浅拷贝(shallow copy),然后转换已分配内存的权限,从而防止悬挂指针和内存泄露。

移动语义是通过右值引用实现的。在C++中,左值是可以获取其地址的一个量,例如有名称的变量。由于经常出现在赋值语句的左边,因此称其为左值。所有不是左值的量都是右值,例如常量、临时变量或者临时对象。通常位于赋值运算符的右边。

右值引用是一个对右值的引用。特别地,这是一个当右值是临时对象时使用的概念。右值引用的目的是提供在临时对象时可选用的特定的方法。由于知道临时对象会被销毁,通过右值引用,某些涉及复制大量值的操作可以通过简单地复制指向指向这些值的指针实现。

函数可将&&作为参数说明的一部分(例如T&& name)来指定右值引用参数。

下面看如何对上面的Example对象赋予移动语义:

添加移动构造函数和移动赋值运算符重载函数:

Example(Example&& other);
Example& operator=(Example&& rhs);


Example::Example(Example&& other)
{
count = other.count;
pNumber = other.pNumber;

other.count = 0;
other.pNumber = nullptr;

cout << "移动构造函数..." << '\n';
}

Example& Example::operator=(Example&& rhs)
{
if (this == &rhs) return *this;

count = rhs.count;
delete pNumber;
pNumber = rhs.pNumber;

rhs.count = 0;
rhs.pNumber = nullptr;

cout << "移动赋值运算..." << '\n';

return *this;
}


我们写个主函数进行测试:

int main()
{
Example demo;
demo.count = 10;
demo.pNumber = new int[demo.count];
for (int i = 0; i < demo.count; i++)
demo.pNumber[i] = i + 1;

Example other = demo.clone();
other.pNumber[0] = 100;

cout << demo.pNumber[0] << '\n';
cout << other.pNumber[0] << '\n';

return 0;
}


运行结果如下:



个人感觉使用移动语义,主要是必满了临时对象的多次拷贝,提高了程序运行效率。

下面来看一个交换两个对象的swap函数,这是一个经典的使用移动语义提高性能的示例。下面的是没有使用移动语义的函数:

void swap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}


这种实现首先将a复制到temp,然后将b复制到a,最后将temp赋值到b。如果类型T的复制开销很大,这个交换实现严重影像性能。使用移动语义,swap函数可以避免所有的复制。

void swap(T& a, T& b)
{
T temp = std::move(a);
a = std::move(b);
b = std::move(temp);
}


std::move可以将左值转换为右值。

参考资料:http://blog.csdn.net/niteip/article/details/37814483
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: