1. 左值与右A值
从汇编角度来讲
左值是有实际内存存储地址的值, 而右值是寄存器里的值(简单理解)。
从面向对象来讲
当一个对象被用作右值的时候,用的是对象的值(内容)。 当一个对象被用作左值的时候,用的是对象身份(在内存中的位置)。
使用关键字decltype时, 左值与右值是不同的。 这个留坑。
2. 右值引用
右值引用有一个很重要的性质————只能绑定到一个即将销毁的对象上。因此,我们可以用一个const 的左值引用或者一个右值引用绑定到这类表达式上。
左值持久, 右值短暂。
1
2
3
4
int i = 42;
int &r = i;
const int &r3 = i * 42;
int &&r4 = i * 42;
3. swap操作
若果一个类没有自定义的swap操作, 那么会调用std::swap。
copy and swap 的本质还是: 为你打算修改的对象做出一个副本,然后在那副本上做一切必要的修改。若有任何修改动作抛出了异常,原对象仍然保持为改变状态。带所有改变都成功后,做出一个不抛出异常的swap动作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class StrDemo {
public:
friend void swap(StrDemo&, StrDemo&);
StrDemo(const StrDemo &s) : str(s.str) {
}
StrDemo& operator=(StrDemo s);
private:
string str;
};
StrDemo& StrDemo::operator=(StrDemo s) {
swap(*this, s);
}
inline
void swap(StrDemo &d1, StrDemo &d2) {
using std::swap;
swap(d1.str, d2.str);
}
4. std::move函数
虽然不能将一个右值引用直接绑定到一个左值上,但是可以显示的将一个左值转换为对应的右值引用。
` int &&rr2 = std::move(rr1);`;
除了对rr1赋值或者销毁以外, 不能在对rr1有任何假设。
5. 移动构造与移动赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class D {
public:
~D() {
}
D(D &&d):s(d.s) { // 移动构造
d.s = nullptr;
}
D& operator=(D &&d) { // 移动赋值
if (this != &d) {
s = d.s;
d.s = nullptr;
}
return *this;
}
private:
string s;
};
从一个对象移动数据并不会销毁次对象, 但是有时候移动操作完成后,源对象会被销毁。
必须要保证源对象移动后可进入一个析构的状态。
移动操作必须保证源对象仍然是有效的, 其可以指向其他的对象。
6. 最后一些细节
合成移动操作
编译器不会为某些类合成移动操作,如果一个类没有移动操作,通过正常的函数匹配,类会用拷贝操作来代替移动操作。
当一个类没有定义任何版本的拷贝控制成员,且类的每个非static 数据成员都可以移动时, 编译器会合成移动构造或者移动赋值运算符。
如果没有移动构造函数, 右值也被拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
class Person {
public:
Person(const string &name) : name(name) {}
Person(const Person &p):name(p.name) {
cout << "拷贝构造函数" << endl;
}
private:
string name;
};
Person p("jay");
Person p2(p); // 拷贝构造
Person p3(std::move(p)); // 拷贝构造
copy and swap 赋值运算符和移动操作
赋值运算符既是移动赋值运算符, 又是拷贝赋值运算符。
单一的赋值运算符实现了两种功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Person {
public:
friend void swap(Person&, Person&);
Person(const string &name) : name(name) {}
Person(const Person &p):name(p.name) {
cout << "拷贝构造函数" << endl;
}
Person(Person &&p) noexcept:name(p.name) {
cout << "移动构造函数" << endl;
p.name = nullptr;
}
Person& operator=(Person p) { // 拷贝构造或者移动构造
swap(*this, p);
return *this;
}
private:
string name;
};
void swap(Person &p1, Person &p2) {
using std::swap;
swap(p1.name, p2.name);
return;
}
int main() {
Person p("jay");
Person p2("jj");
p2 = p; // 拷贝构造函数
p2 = std::move(p); // 移动构造函数
}
右值引用和成员函数
vector 的两个push版本
1
2
void push_back(const X&); // 绑定任意类型
void push_back(X&&); // 绑定右值
当我们调用push时, 编译器根据类型选择调用那个版本。
1
2
3
vec.push_back("hey"); // 调用右值
string s = "jay";
vec.push_back(s); // 调用左值
重载和引用函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Foo {
public:
Foo sorted() &&; // 用户右值
Foo sorted() const &; // 用于任何类型的Foo
private:
vector<int> data;
};
// 对象为右值 可以原地排序
Foo Foo::sorted() &&{
sort(data.begin(), data.end());
return *this;
}
// 对象为左值 拷贝后排序
Foo Foo::sorted() const &{
Foo f(*this);
sort(f.data.begin(), f.data.end());
return f;
}