今天我们主要讨论C++11的移动语义和右值引用,其中包含一些问题,什么是移动语义?,C++11如何支持他,为什么需要移动语义?强制移动move函数,本章主要讨论以上问题,有兴趣的可以继续往下阅读。
为什么需要移动语义?
我们先来看看这段C++11之前的代码
string a(1000, 'a');//对象a有1000个字符
假设他有一个函数将a中的1000个字符反转一下返回一个新的对象。
string Revarsal(string & str)
{
string temp;
//do something;
return temp;
}
接下来,我们使用这种方式使用它
string a(1000, 'a');
string c(Revarsal(b));
我们来探讨一下 string c(Revarsal(b)); 这句代码,首先这个revarsal函数创建了一个临时变量temp,然后revarsal程序返回之后删除temp(迟钝的程序可能会将temp赋给一个临时对象,再将这个临时对象返回出去),考虑到中间的数据在不同的对象之间拷贝,在时间和空间方面都有很多的浪费。如果这个时候程序直接将temp的所有权转移给c不是更好吗?也就是说我们不将temp的1000个字符串转移到其他的地方,再删除原来的字符,而是将原来的字符留在原地,只是修改这段字符的所有权拥有者,这种方法被称之为移动语义。
要实现移动语义,就要采取某种方式,让编译器知道何时该调用移动语义,什么时候是复制,什么时候是移动,可以定义两个构造函数,一个就是常规的复制构造函数,一个就是移动复制构造函数。
复制构造函数只是执行深复制,而移动构造只是调整记录,再将所有权转移给新对象,所以移动构造可能修改其实参,这意味着右值引用不应该为const类型。
下面演示一个简单的移动语义的例子
//这是类的声明
class Move
{
public:
Move(int len);
Move(const Move & m);
Move(Move&& m);
Move operator +(const Move& m) const;
~Move();
private:
int n;
static int m_Count;
int * m_str;
};
#include "Move.h"
#include <iostream>
using namespace std;
int Move::m_Count = 0;
Move::Move(int len)
{
n = len;
m_Count++;
m_str = new int[len];
cout << "Move(int )" << endl;
cout << m_Count << " " << n << " " << (void*)m_str << endl<<endl;
}
Move::Move(const Move& m)
{
n = m.n;
m_Count++;
m_str = new int[n];
cout << "Move(const Move &)" << endl;
cout << m_Count << " " << n << " " << (void*)m_str << endl << endl;;
}
Move::Move(Move && m)//移动语义构造函数
{
m_str = m.m_str;
m.m_str = nullptr;
m.n = 0;
cout << "Move &&" << endl;
cout << m_Count << " " << n << " " << (void*)m_str << endl << endl;
}
Move Move::operator +(const Move& m) const
{
Move temp(n + m.n);
return temp;
}
Move::~Move()
{
n = 0;
delete[] m_str;
}
强制移动move函数,
移动构造函数和移动赋值运算符使用右值。如果要让它们使用左值,该如何办呢? 上图的move b(a) 如果可以让用户来选择是否使用移动构造函数或移动赋值运算符来保留选定的对象那该多好。为此,可使用static_case<>将对象的类型强转为Move &&,但是C++11提供了一种更为简单的方法,使用头文件中utility中声明的函数std::move().使用如下
总结
对于大多数程序员来说,右值引用带来的好处并非是他们能够编写使用右值引用的代码,而是能够利用右值引用实现移动语义的库代码,例如,STL类现在都有复制构造函数,移动构造函数,辅助运算符和移动移动复制运算符。
后面几章将会分享C++11中新的类功能,以及lambda函数,敬请关注。