1.左值和右值
C++11中的定义:左值表达式表示的是一个对象的身份(在内存中的位置),而右值表达式表示的是对象的值(内容)。
- 左值和右值都是针对表达式而言的,左值是持久的,右值是短暂的:左值在表达式结束后仍然存在,右值在表达式结束后会被销毁。
- 区分左值和右值的方法:看能不能进行取地址操作,若能,则为左值,否则为右值。
1.1右值:
void fun(int &x) {
//
}
int main() {
fun(10);
return 0;
}
编译的时候提示:
error: invalid initialization of non-const reference of type ‘int&’ from an rvalue of type ‘int’
报错的rvalue就是10,(右值的一个特性,无法取址)
1.2左值
void fun(int &x) {
//
}
int main() {
int x=10;
fun(x);
return 0;
}
这样x就是左值了。
注意:在需要右值的地方可以用左值来代替,但是不能把右值当成左值(也就是位置)使用。
1.3消亡值
**prvalue右值引用(&&),**用于计算的或者用于初始化对象的,p代表的是Pure。
lvalue左值可被取址,指在内存中具有位置的值。
那么消亡值,xvalue又是什么?
**xvalue 就是临时变量(temporary object)。**它是快要被销毁的值,x 代表expiring,所以可以被重新使用。
常见的xvalue有:由prvalue实体化创建的临时变量,函数内的局部变量被return的时候(在它生命的最后时刻);std::move修饰的表达式;返回类型为T&&的函数的调用, T&& f()。
1.4总结:
每一类值对应不同的构造方法:
prvalue初始化lvalue,是直接构建,不用调用拷贝或移动构造函数;
xvalue初始化lvalue,会调用移动函数,也就是move;
lvalue初始化lvalue,会调用拷贝构造函数。
- xvalue可以被直接move;
- prvalue被move的时候,可以理解生成了一个临时变量xvalue,这个临时变量xvalue被move了;
- 如果move lvalue,那么需要使用std::move将lvalue变成xvalue,从而move生成后的xvalue。
移动语义
- C++11标准引入了“对象移动”的概念
- 对象移动的特性是:可以移动而非拷贝对象
- 在C++旧标准中,没有直接的方法移动对象。因此会有很多不必要的资源拷贝。
一个例子(移动拷贝构造):我放风筝,有人想玩风筝,我直接把风筝线交给这个人。风筝在空中,一直没有被移动,但所有权已经从我手中交给另一个人。这就是move。
move:原使用对象不再使用,直接转接对象的所有权。
example:
int gI = 0;
class Book {
public:
int m{3};
Book() {
std::cout << gI << ":"
<< "default constructor\n";
}
Book(Book &&b) {
std::cout << gI << ":"
<< "move constructor\n";
}
Book(const Book &b) {
std::cout << gI << ":"
<< "copy constructor\n";
}
Book &operator=(const Book &) {
std::cout << gI << ":"
<< "copy assigment\n";
return *this;
}
Book &operator=(Book &&) {
std::cout << gI << ":"
<< "move assignment\n";
return *this;
}
};
int main() {
std::cout<<"-------"<<endl;
std::cout << gI << "-";
Book bb = Book(); // prvalue直接初始化 default constructor
gI = 1;
std::cout << gI << "-";
Book b; // default constructor
gI = 2;
std::cout << gI << "-";
b = Book(); // 移动 pravlue
gI = 3;
std::cout << gI << "-";
bb = std::move(b); // lvalue转换成xvalue,然后移动
}
输出:
-------
0-0:default constructor
1-1:default constructor
2-2:default constructor
2:move assignment
3-3:move assignment
另外要注意的是,右值引用是对rvalue的引用,但是这个引用却是lvalue,所以下面的代码如果要调用T类型的移动函数,要这么写,
void f(T&& v) {
T v = std::move(v); //要调用移动函数,但是v是lvaue,所以我们要使用std::move
}
理解的关键是,右值引用是一个类型,而v是一个变量,所以是左值——一个引用了右值的左值变量。所以我们需要在前面加上std::move。
std::string s =" hell world";
auto left = std::move(s);
cout<<s<<endl;
s已经是空值了,不建议再使用s这个变量
所以,C++的move改变了值的所有权
2.move的使用:
1.接管资源
void My::take(Book && iBook)
{
mBook = std::move(iBook); //将没人要的iBook,拿过来据为己有
}
2.转移所有权
auto thread = std::thread([]{});
std::vector<std::thread> lThreadPool;
lThreadPool.push_back(std::move(thread)); //现在thread pool来掌控着thread
3.避免拷贝
void f() {
std::vector v = ...;
take(std::move(v)); // 直接move进了函数g里面,不用拷贝
}
参考资料:
move