参考文献
- cnblogs.com/sunchaothu/p/11343517.html
- https://www.zhihu.com/question/363686723/answer/1976488046
为了导入右值和移动语义,首先复习了以下临时对象在函数返回值和传参数时构造了几次;然后对比介绍了左值和右值,以及右值引用的形式和含义。为移动语义和完美转发的介绍做铺垫。
C++11 引入了 std::move 语义、右值引用、移动构造和完美转发这些特性。
// main.cpp
#include
using namespace std;
class A{
public:
A(){
cout<<"class A construct!"<
使用 g++ 编译;注意使用 -fno-elide-constructors关闭省略构造优化
g++ main.cpp -fno-elide-constructors
可以得到以下输出
class A construct!
class A copy!
class A destruct!
class A copy!
class A destruct!
class A destruct!
可以看到A a=get_A_value(); 一行代码居然产生1次对象构造和2次对象的拷贝构造!具体为
如果使用编译器优化(默认), 则会把临时对象拷贝的那次 和 用返回值构造最终对象的拷贝的给省略了;也即,只有一次构造和析构。
class A construct!
class A destruct!
// ... A
void pass_A_by_value(A a){
}
int main(){
A a;
pass_A_by_value(a);
return 0;
}
在去掉优化 g++ main.cpp -fno-elide-constructors时输出为
class A construct!
class A copy!
class A destruct!
class A destruct!
事实上,在未经优化的情况下,以下时候拷贝构造函数会被调用:
对象的频繁构造是程序的开销,特别是当对象内部有堆上内存(比如有 new 出来的成员)的时候,每次拷贝构造的时候都需要用 new 申请一块内存,造成性能的降低。对于情况2,好习惯是如果函数参数是只读的(也即不会在程序内进行修改),传引用作为参数,也即 pass_A_by_refrence(const A &a); 对于情况1,编译器会为我们进行优化; 对于情况3,C++11 引入了一种移动构造函数的概念,它将获取**右值引用*,右值的“资源” move 到新对象中,这个过程中不会申请新的内存,从而达到提高了效率和性能。
所以,要理解些关键词 “移动构造”、“移动语义” ,首先要理解右值和右值引用。
左值的英文简写为“lvalue”,右值的英文简写为“rvalue”。很多人认为它们分别是"left value"、“right value” 的缩写,其实不然。lvalue 是“loactor value”的缩写,可意为存储在内存中、有明确存储地址(可寻址)的数据,而 rvalue 译为 “read value”,指的是那些可以提供数据值的数据(不一定可以寻址,例如存储于寄存器中的数据)。
A a = foo(); // foo() 为右值
int a = 5; //5字面量为右值
char *x = "thu"; // “thu”为字面值也为右值
a = b + c; // b + c这个结果也是一个右值
return a //临时对象,也即函数返回值的时候只会“临时”存在的对象(运行超过那一行就会结束它的生存期),这个临时返回值就是一个右值;
位于赋值运算符 = 右边的值,为右值;在左边的则为左值
左值可以取得地址、有名字; 不可以取得地址、没有名字的为右值。所以 A a = foo()可以用 &a取得a的地址,a 是左值,然是不能取得 foo()的地址,(&foo())无法通过编译, foo()返回的临时对象也是没有名字的,所以是右值。
在C++11中,右值包括两种,一中是将亡值(xvalue, eXpiring Value),一种是纯右值(prvalue,Pure Rvalue)[1]。函数非引用返回的临时对象、运算表达式的结果、1, 3.14,'c’这样的字面值等都属于纯右值。而xvalue则是由 C++11引入的 如返回值为 A&& 的函数返回值或者std::move()的返回值等。
左值引用就是一般的引用,一般用一个&表示,例如
const A &a_ref = a; // 取得对象 a 的引用
左值引用相当于别名,指向一个具体的对象。
右值引用顾名思义,就是右值的引用, 用 &&表示;
A &&r_ref = getRvalue(); // r_ref 是一个右值引用
右值引用也相当于别名,与左值的区别为右值引用是无名变量的别名。
getRvalue() 是一个返回右值的函数,右值在这一句执行完就该结束他的生存期了,如果是对象就该调用析构函数了;但是右值引用让它强行续命;使用右值引用指向右值,右值的生存期和右值引用一样长了,这也就少一次对象的析构和构造了。
C++的右值引用主要有两个用处,
int num = 10;
const int &b = num;
const int &c = 10;
int num = 10;
//int && a = num; //右值引用不能初始化为左值
int && a = 10;
int && a = 10;
a = 100;
cout << a << endl;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YC7uKOY4-1692172992508)(image/2021-07-21-11-45-51.png)]
右值引用(Rvalue Reference),与传统的左值引用(Lvalue Reference)相对,是绑定到右值的引用。int n;
int & lref = n;
int && rref = 1;
右值引用是 C++ 11 标准引入的语言特性,理解右值引用,对理解现代 C++ 语义至关重要。而这一概念又经常让人困惑,例如在下面的例子 “overload foo” 中,函数 foo 的调用会匹配哪一个重载?void
foo(int &) { std::cout << "lvalue" << std::endl; }
void foo(int &&) { std::cout << "rvalue" << std::endl; }
int main() {
int &&rref = 1;
foo(rref); // which overload will be seleted?
}
答案是匹配 foo(int&),是不是和想象中的不太一样?
C++ 中的表达式有两个维度的属性:类型(type)和值类别(value category)。
类型这个概念就是我们熟知的类型,int, int *, int &, int && 都是类型;其中 int 称为基础类型(fundamental type),后三者称为复合类型(compound type)。
值类别分为:左值(lvalue)、亡值(xvalue) 和 纯右值(prvalue),任一个表达式都可以唯一地归类到这三种值类别中的一个,这三种类别称为基础类别(primary category)。除了这三种基础类别外,还有两种混合类别(mixed category):泛左值(glvalue)和右值(rvalue),泛左值包括左值和亡值,右值包括亡值和纯右值。
纯右值
亡值
左值
泛左值
概念
对应
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8kn0hYXk-1692172992508)(image/2021-08-07-13-48-45.png)]
void foo(int &) { std::cout << "lvalue" << std::endl; }
void foo(int &&) { std::cout << "rvalue" << std::endl; }
int main() {
int &&rref = 1;
foo(rref); // output: lvalue
}
More importantly, when a function has both rvalue reference and lvalue reference overloads, the rvalue reference overload binds to rvalues (including both prvalues and xvalues), while the lvalue reference overload binds to lvalues.
变量 rref 其实是一个左值,它的类型是 int 的右值引用 (int&&),它绑定到一个右值(字面量1),但它本身是一个左值。一方面因为右值没有名字。另一方面,因为右值引用(值类型)是左值(值类别)并不冲突。