某些情况下,对象拷贝后就会立即被销毁,此时,移动而非拷贝对象会大幅度提升性能。
右值引用(rvalue reference)
int i = 42;
int &&r = i * 42; //将右值引用绑定到一个右值上
- 返回左值引用的函数,赋值,下标,解引用,前置递增/递减运算符->返回左值。
- 返回非引用类型的函数,算术,关系,位,后置递增/递减运算符->返回右值。
- 左值表示一个对象的身份,而一个右值表示对象的值。
- 右值引用只能绑定到临时对象上去
- 右值引用指向即将被销毁得分对象。来移动对象。
- 用右值引用来表示移动语义->定义移动构造函数,移动赋值运算符
标准库move函数
- std::move® 获得绑定到左值上的右值引用。
- 调用move就意味着:除了对它进行赋值或销毁它外,不再使用它。
- 注意:应使用std::move而不是move
移动构造函数
Foo::Foo(Foo &&rhs) noexcept //禁止其抛出异常
{
ptr1_member = rhs.ptr1_member;
ptr2_member = rhs.ptr2_member;
//将rhs置于可析构状态
rhs.ptr1_member = rhs.ptr2_member = nullptr;
}
移动赋值运算符
Foo &Foo::operator=(Foo &&rhs) noexcept
{
if(this != &rhs){
free(); //释放已有元素
ptr1 = rhs.ptr1;
ptr2 = rhs.ptr2;
//将rhs置于可析构状态
rhs.ptr1 = rhs.ptr2 = nullptr;
}
return *this;
}
- 完成资源移动后,源对象必须不再指向被移动的资源。
- 完成移动操作后,移后源对象必须保持有效的(可以安全地为其赋予新值或可以安全地使用而不依赖其当前值)、可析构的状态,但是用户不能对其值进行任何假设。
- 不抛出异常的移动构造函数和移动赋值运算符需标记为noexcept。
合成的移动操作
- 编译器合成移动构造函数/赋值运算符的条件:
- 类没有自己版本的拷贝控制成员
- 类的每个非static数据成员都可以移动
- 如果类定义了一个移动构造函数和/或一个移动赋值运算符,则该类的合成拷贝运算符和拷贝赋值运算符会被定义为删除的。
移动右值,拷贝左值。若没有移动构造函数,右值也被拷贝。
拷贝并交换赋值运算符
Foo& Foo::operator=(Foo rhs){
swap(*this, rhs);
return *this;
}
- 即实现了拷贝赋值运算符,也实现了移动赋值运算符。
移动迭代器(move iterator)
- make_move_iterator函数将一个普通迭代器转换为一个移动迭代器。
- 对其解引用返回右值引用。
- 标准库不保证那些算法使用移动迭代器。
为成员函数同时提供拷贝版本和移动版本
void push_back(const X&); //拷贝
void push_back(X&&); //移动
区分调用成员函数的对象是左值还是右值
- 在参数列表后放置一个引用限定符(reference qualifier), & 或 &&。
Foo &Foo::operator=(const Foo &rhs) & //加一个&,限定只有左值可以调用该函数,即限制只可以赋值给左值
{
//执行赋值操作...
return *this;
}
- &限制只能用于左值
- &&限制只能用于右值
- 右const时,放在const之后
综合const和引用限定符来重载函数
class Foo{
public:
//&&:用于右值的版本
Foo sorted() &&
{
//对象本为右值,可以原址排序
sort(data.begin(), data.end());
}
//可以用于任何类型
Foo sorted() const &
{
Foo ret(*this);
sort(ret.data.begin(), ret.data.end());
return ret;
}
private:
DATA data;
}