为什么使用Move而不是Copy
在一些我们需要进行复制操作的代码语句中,被复制的对象在被复制后很快就被删除不再使用了,在这种情况下,拷贝一份数据显然产生了不必要的开销,因此便产生了对移动数据的需求。
前置知识:左值右值
左值与右值
笼统的来说,左值即可以放到等号左边的东西,而右值是只能放在等号右边的东西,C++中,我们可以获取左值的地址而无法获取右值的地址
通常,我们自己定义的变量都是左值。左值一般都是需要长期存在的(变量可以进行后续操作),而右值的生命周期是短暂的,理应在一次调用后便销毁(eg. a = 4, 我们不会关心4是否存在内存中,或是存在什么位置)
eg. 在下面的代码中,a,b,c均为左值,而4为右值,我们无法写做4 = a
int a = b + c;
int a = 4;
左值引用和右值引用
C++中,我们常常使用&来表示一个变量为引用类型,这种引用通常被称为左值引用,顾名思义,左值引用引用的对象应该是一个左值。其中有一个例外,若引用被定义为一个常值,则它可以作为一个右值的引用。
相对应的,C++11引入了右值引用,用&&来表示,这种引用的对象只可为右值。
eg.
int a = 0
int& b = a; ok
int& c = 5; error: 左值引用不可引用右值
int&& d = a; error: 右值引用不可引用左值
int&& e = 5; ok
const int& f = 5; ok: 常值引用可以引用右值
C++库中的move方法
在C++中,如果我们想将一个左值作为一个右值来进行操作,我们可以调用std::move方法,在进行move操作后,我们不保证被移动的变量的后续情况如何
int a = 1;
int&& rr = std::move(a);
移动构造器和移动赋值运算符
为了对我们自己构造的类使用移动操作,我们需要自己构造移动构造器和移动赋值运算符。移动操作类似与拷贝操作,但不同的是,移动到的对象会将被移动的对象的资源"偷走"。在进行了移动操作后,应保证不再访问被移动的对象,此外还需保证被移动对象可以被安全的删除而不影响到其他对象。
移动构造器
移动构造器传入的参数是一个右值引用(暗示了被引用的对象是临时的,后续不再使用)
eg. StrVec的移动构造器将原先对象的指针都赋值到了新的对象中,并将原先对象的指针设为了空值,确保原先对象能正常被删除
StrVec::StrVec(StrVec &&s) noexcept // move won't throw any exceptions
// member initializers take over the resources in s
: elements(s.elements), first_free(s.first_free),
cap(s.cap)
{
// leave s in a state in which it is safe to run the destructor
s.elements = s.first_free = s.cap = nullptr;
}
移动赋值运算符
类似于移动构造器,移动赋值运算符传入的参数也是一个右值引用。同时,类似与拷贝赋值运算符的是,我们需要保证自己移动到自己时的可用性。
StrVec &StrVec::operator=(StrVec &&rhs) noexcept
{
// direct test for self-assignment
if (this != &rhs) {
free(); // free existing elements
elements = rhs.elements; // take over resources from rhs
first_free = rhs.first_free;
cap = rhs.cap;
// leave rhs in a destructible state
rhs.elements = rhs.first_free = rhs.cap = nullptr;
}
return *this;
}