C++11:右值引用,实现移动语义和完美转发

目录

1、右值引用

2、移动语义(std::move)

3、完美转发(std::forward)


1、右值引用

右值引用(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

2、移动语义(std::move)

(1)移动语义含义:转移资源所有权,类似转让或者资源窃取,对于那块资源,转为自己所拥有,别人不再拥有也不会再使用;理解深浅拷贝,直接把原来需要拷贝的内存易主

在C++11之前,我们拥有4个特殊成员函数,即构造函数、析构函数、拷贝构造函数以及拷贝赋值运算符。从C++11开始,我们多了2个特殊成员函数,即移动构造函数和移动赋值运算符。

在C++11之后,如果我们定义一个空类,除了之前的4个特殊成员函数,编译器还会为我们生成移动构造函数和移动赋值运算符: 

但是我们自定义一些函数时候,可能就不一定会自动生成移动构造函数和移动赋值运算符,相关的关系看下面的表:

C++11:右值引用,实现移动语义和完美转发_第1张图片

(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的情况下,会尝试执行移动构造函数,最后才是开销最大的拷贝构造函数。

3、完美转发(std::forward)

(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(arg) 将返回 arg 的左值引用,而当 T 为 int&& 时,std::forward(arg) 将返回 arg 的右值引用。

你可能感兴趣的:(#,c++新特性,c++,开发语言)