右值引用
一般的右值引用指向将要被销毁的对象,比如返回非静态数据的函数返回值,
右值引用就是必须绑定到右值得引用,只能绑定到一个将要销毁的对象
一般而言,一个左值表达式表示的是一个对象的身份,而右值表达式表示的是对象的值
不能将左值引用绑定到要求转换的表达式、字面常量或返回右值的表达式,
int i = 7;
int& r = i;
int&& rr = i;//错误 不能将右值引用绑定到一个左值上
int& r2 = i * 7;//错误 i*7是一个右值,
const int& r3 = i * 42;//可以将一个常量的引用绑定到一个右值上
int&& rr2 = i * 42;//将rr2绑定到乘法结果上
左值有持久的状态,而右值要么是字面常量要么是在表达式求值过程中创建的临时对象。变量是左值
在头文件utility中有move函数可以获得绑定到左值上的右值引用
int&& rr1 = 42;//右值引用
int&& rr3 = std::move(rr1);//rr1为变量,是左值
rr1++;
cout << rr1 << endl;//43
cout << rr3 << endl;//43
移动构造函数和移动赋值运算符
与拷贝构造函数不同,移动构造函数不分配任何的新内存;它接管给定的类中的内存。
在接管内存之后,它将给定对象中的指针都置为nullptr。
class strvec
{
public:
strvec(strvec&&) noexcept;//移动构造函数
strvec& operator=(strvec&& rhs) noexcept;//移动赋值运算符
private:
int x;
int y;
};
strvec::strvec(strvec&& s)noexcept//移动操作不应抛出任何异常,必须标记为noexcept
:x(s.x), y(s.y)//成员初始化器接管s中的资源
{
s.x = s.y = nullptr;
}
strvec& strvec::operator = (strvec&& rhs) noexcept
{
if (this != rhs)
{
free();//释放已有元素
x = rhs.x;//接管资源
y = rhs.y;
//将rhs置于可析构状态,移后源对象必须是可析构的
rhs.x = rhs.y = nullptr;
}
return *this;
}
合成的移动操作
与拷贝操作不同,编译器不会为某些类合成移动操作。
只有当一个类没有定义任何自己版本的拷贝控制成员,
且它的所有数据成员都能移动构造或移动赋值时,编译器才会合成移动构造函数或移动赋值运算符
struct X {
int i;//内置类型可以移动
string s;//string定义了自己的移动操作
};
struct hasX {
X mem;//X有合成的移动操作
};
X x, x2 = std::move(x);//使用合成的移动构造函数
hasX hx, hx2 = std::move(hx);//使用合成的移动构造函数
移动操作永远不会隐式定义为删除函数
定义了一个移动构造函数或移动赋值运算符的类必须也定义自己的拷贝操作。
否则这些成员默认地被定义为删除的。
假定Y是一个类,它定义了自己的拷贝构造函数但未定义自己的移动构造函数
struct hasY {
hasY() = default;
hasY(hasY&&) = default;
Y mem;//hasY将有一个删除的移动构造函数
};
hasY hy, hy2 = std::move(hy);//错误,移动构造函数是删除的
移动右值,拷贝左值,但如果没有移动构造函数,右值也会被拷贝。
class Foo {
public:
Foo() = default;
Foo(const Foo&);
};
Foo x;
Foo y(x);//拷贝构造函数,x是一个左值。
Foo z(std::move(x));//拷贝构造函数,因为未定义移动构造函数
拷贝并交换赋值运算符和移动操作
class Hclass {
public:
//添加的移动构造函数
Hclass(Hclass&& p) noexcept : x(p.x), y(p.y) { p.x = 0; };
//赋值运算符既是移动赋值运算符,也是拷贝赋值运算符
Hclass& operator= (Hclass rhs)
{
swap(*this, rhs);
return *this;
}
private:
int x;
int y;
};
右值引用和成员函数
区分移动和拷贝的重载函数通常有一个版本接受一个const T&,而另一个版本接受一个T&&。
class strstr {
public:
void push_back(const string&);
void pusb_back(string&&);
};
右值和左值引用成员函数
class Foo {
Foo& operator=(const Foo&) &;//只能向可修改的左值赋值
Foo somMem()& const;//错误 const限定符必须是左值
Foo anotherMem() const &;//正确 const限定符在前
};
Foo& Foo::operator=(const Foo& rhs) & {
//执行将rhs赋予本对象所需的工作
return *this;
}
Foo& retFoo();//返回一个引用,retFoo调用的是一个左值
Foo retVal();//返回一个值,retVal调用的是一个右值
Foo i, j;//i,j都是左值
i = j;//
retFoo() = j;//正确 retFoo返回的一个左值
retVal() = j;//错误 retVal返回的一个右值
i = retVal();//正确,我们可以将一个右值作为赋值操作的右侧运算对象
重载和应用函数
class Foo {
public:
Foo sorted() &&;//可用于可改变的右值
Foo sorted() const &;//可用于任何类型的Foo
private:
vector<int> data;
};
本对象为右值,因此可以原址排序
Foo Foo::sorted() &&
{
sort(data.begin(), data.end());
return *this;
}
本对象是const或是一个左值,哪种情况我们都不能对其进行原址排序
Foo Foo::sorted() const &
{
Foo ret(*this);//拷贝一个副本
sort(ret.data.begin(), ret.data.end());//排序副本
return ret;//返回副本
}