c++ 右值引用小结
c++ 右值引用
左值 (lvalue):
- 左值表示的是内存中有明确可识别地址的对象,即可以取地址的表达式。
- 左值可以出现在赋值语句的左边或右边。例如,如果我们有
int a = 1;
那么a
是一个左值表达式,因为你可以对它进行操作如a = 2;
。 - 左值通常可以持久存在直到其作用域结束。
右值 (rvalue):
- 右值表示的是临时的、不具有明确存储地址的对象,通常不能取地址,即通常不可以出现在赋值符号的左边。
- 右值表示的对象是在表达式评估过程中产生的临时值,它们通常用于初始化左值或者与左值进行比较,或者就是作为一个临时值。
- 例如,在表达式
int x = 1 + 2;
中,1 + 2
就是一个右值,它产生一个临时值,在赋值给左值x
之后,该临时值就不再需要了。 - 右值还可以进一步分为纯右值(prvalue)和将亡值(xvalue)。纯右值是指那些非引用返回的临时量,没有实体与之对应的表达式的值,而将亡值通常是与对象生命周期结束相关的表达式,如
std::move
操作产生的值。
右值引用
C++11中引入了右值引用的概念,用来支持移动语义(Move Semantics)和完美转发(Perfect Forwarding)。右值引用可以绑定到一个右值上,允许程序员安全地从右值中窃取资源。右值引用使用 &&
符号标记。例如,
几个应用场景
支持移动语义 (Move Semantics):
在C++11之前,对象数据只能通过复制进行传递,这可能会导致性能问题,尤其是对于大型数据结构。右值引用和移动语义使得资源(如动态内存、文件句柄、网络连接等)可以从一个对象转移至另一个对象,而不需要复制数据。例如,标准库中的
std::vector
可以使用移动构造函数来接管另一个std::vector
的内部数组,避免不必要的复制:在这个例子中,
original
现在是空的,moved_to
接管了原来original
的数据。更实际一些的例子:
完美转发 (Perfect Forwarding):
在模板编程中,有时需要完整无缺地传递参数给另一个函数。右值引用,特别是与模板和std::forward
一起使用时,可以保持参数的左值或右值属性。以下是一个采用完美转发的函数模板例子:
这里,如果
relay
的参数是左值,arg
将作为左值传递给someOtherFunction
;如果是右值,将作为右值传递。优化临时对象的处理:
如果函数对临时对象做特殊处理,右值引用可以优化这些情况。例如,一个接收右值引用的函数重载可以决定采取更高效的途径,包括窃取资源或者避免不必要的深拷贝。
当你有一个右值(比如临时对象或经过 std::move
转换的对象)时,使用右值引用可以大大提高性能,因为它允许资源的重新利用而不是资源的复制。这是现代C++编程中一个重要的优化手段。
右值引用提升代码性能应用场景
线程安全的移动操作:
如果需要将对象从一个线程传递给另一个线程,使用右值引用和移动语义可以避免复制对象,尤其是对于大的对象或包含资源如动态分配内存、文件句柄的情况。例如,std::thread
的构造函数就可以接受右值引用参数,从而允许直接在新线程中使用传入对象的资源:线程池任务队列:
对于线程池的实现,任务通常需要被放入队列中等待执行。如果任务是以函数对象的形式存储的,那么通过std::function
可以对它们进行封装。如果这些函数对象支持移动操作(如使用std::bind
生成的函数对象),那么使用右值引用将任务添加到队列中时可以避免复制,提升效率。std::function
代码片段:std::function
使用
std::async
:std::async
可以异步的执行任务。如果传递给std::async
的函数接受的是重对象或者资源的所有权,那么使用右值引用可以避免不必要的复制。在这个场景中,使用
std::move
可以直接将huge_data
移动到异步任务中,减少数据的复制。使用
std::promise
和std::future
:
你可以使用右值引用来传递std::promise
对象到线程中,并在结束时设置值。右值引用允许将std::promise
对象的所有权从一个线程传递给正在运行的线程,从而避免复制操作。
上述场景中通过避免过度的对象复制,右值引用的使用可以大幅降低与线程操作相关的系统资源的消耗,因此提高了整个程序的性能。
完美转发机制
完美转发(Perfect forwarding)是C++11中引入的一个技术,允许我们保持转发给函数的参数的类别(值类别,例如左值、右值、常量等)不变。这是通过模板和右值引用来实现的,特别是通过引用折叠规则和std::forward
的使用。这允许开发者编写接受任意参数并将其转发到另一个函数,同时保留其值类别的函数。
完美转发简单demo:
Code Snippet
std::function
code refactor 见 std::forward replaces std::bind
std::forward replaces std::bind
代码中的lambda表达式捕获了一个名为foo
的外部变量(假设是以值捕获,因为在方括号内没有明确指出是用引用捕获)。
这个lambda表达式也是一个通用lambda,能够接受任意类型的参数PH1
,因为它的参数被声明为auto &&
,表示一个完美转发的通用引用。
Lambda表达式的函数体调用了foo
对象的成员函数print_add
,并向它传递了PH1
参数。此处,std::forward<decltype(PH1)>(PH1)
用来完美转发PH1
,保持PH1的原始值类别(lvalue或rvalue)。
这意味着如果f_add_display2
被以右值引用调用,print_add
将接收到一个右值引用,相反如果它被以左值引用调用print_add
将接受到一个左值引用。