目录
1、右值引用
2、移动语义(std::move)
3、完美转发(std::forward)
右值引用(Rvalue reference)是C++11引入的一个新特性,它是一种新的引用类型,用于表示将要被移动的对象或临时对象。
(1)首先了解几个关键名词:
(1)左值:可以取地址的表达式,并且有名字;
(2)右值:不能取地址的表达式,且没有名字;
(3)纯右值:运算表达式产生的临时变量,不和对象关联的原始字面量、非引用返回的临时变量、lambda表达式都是纯右值;
(4)将忘值:即将销毁的值;
(5)左值引用&:对左值进行引用的类型,等号右边的值必须可以取地址
(6)右值引用&&:对右值进行引用的类型,等号右边的值需要是右值,可以使用std::move函数强制把左值转换为右值。
(2)相关的代码展示:
int a = 1; // a是左值
int b = a; // a是左值,b是左值
int c = a + b; // a和b是右值,c是左值
int&& rvalue_ref = 1; // rvalue_ref是右值引用
int &&d = a; // error, a是左值
int &&e = std::move(a); // ok
(1)移动语义含义:转移资源所有权,类似转让或者资源窃取,对于那块资源,转为自己所拥有,别人不再拥有也不会再使用;理解深浅拷贝,直接把原来需要拷贝的内存易主
在C++11之前,我们拥有4个特殊成员函数,即构造函数、析构函数、拷贝构造函数以及拷贝赋值运算符。从C++11开始,我们多了2个特殊成员函数,即移动构造函数和移动赋值运算符。
在C++11之后,如果我们定义一个空类,除了之前的4个特殊成员函数,编译器还会为我们生成移动构造函数和移动赋值运算符:
但是我们自定义一些函数时候,可能就不一定会自动生成移动构造函数和移动赋值运算符,相关的关系看下面的表:
(2)右值引用的主要作用是实现移动语义(Move Semantics),即在对象的拷贝或赋值操作中,将资源的所有权从一个对象转移到另一个对象,避免不必要的拷贝和内存分配。例如:
// 例子1
std::vector vec1 = {1, 2, 3};
std::vector vec2 = std::move(vec1); // 使用右值引用实现移动
// 例子2
std::vector vecs;
...
std::vector vecm = std::move(vecs); // 免
//类的
int main()
{
A a(10);
A b = a;
A c = std::move(a); // 调用移动构造函数
return 0;
}
例子1:在上面的代码中,使用std::move将vec1转换为右值引用,然后将其赋值给vec2。由于vec1已经成为右值,因此可以安全地将其资源(即动态分配的内存)转移给vec2,避免了不必要的拷贝和内存分配。
注意:移动语义仅针对于那些实现了移动构造函数的类的对象,对于那种基本类型int、float等没有任何优化作用,还是会拷贝,因为它们实现没有对应的移动构造函数。
还需要关注的重点在于,我们需要把传入对象A的数据清除,不然就会产生多个对象共享同一份数据的问题。被转移数据的对象会处于"有效但未定义(valid but unspecified)"的状态。原本的指针要置空。
(3)避免非必要的std::move调用
在C++中,存在称为"NRVO(named return value optimization,命名返回值优化)"的技术,即如果函数返回一个临时对象,则该对象会直接给函数调用方使用,而不会再创建一个新对象。
当返回局部对象时,我们不用画蛇添足,直接返回对象即可,编译器会优先使用最佳的NRVO,在没有NRVO的情况下,会尝试执行移动构造函数,最后才是开销最大的拷贝构造函数。
(1)右值引用还可以用于实现完美转发(Perfect Forwarding),即在函数模板中将参数按原样转发给另一个函数。即是:转发函数实参是左值,那目标函数实参也是左值;(右值同理)
(2)使用 std::forward 的示例:
#include
#include
template
void print(T&& arg) {
std::cout << "Printing: " << std::forward(arg) << std::endl;
}
int main() {
int a = 5;
const int b = 10;
print(a); // T is int&, arg is int&
print(b); // T is const int&, arg is const int&
print(15); // T is int, arg is int&&
return 0;
}
在这个示例中,我们定义了一个名为 print 的模板函数,接受一个类型为 T 的参数 arg。在函数体内,我们通过 std::forward 将 arg 转发给 std::cout,保留其原始值类型和常量性质。
在 main 函数中,我们分别调用 print 函数,传递了一个 int 类型的变量 a,一个 const int 类型的变量 b,以及一个 int 字面量 15。当我们传递 a 和 b 时,T 的类型分别为 int& 和 const int&,因此 arg 的类型也分别为 int& 和 const int&。当我们传递 15 时,T 的类型为 int,arg 的类型为 int&&。
在每个调用中,我们使用 std::forward 将 arg 转发给 std::cout,以便正确地保留其原始类型和常量性质。例如,当 T 为 int& 时,std::forward