在构造函数/赋值运算符之外使用r值引用



我目前正在通过阅读教程了解R值引用。许多教程提到移动构造函数/赋值运算符是R值引用的主要用例。所以我想知道它们是否/如何在";规则5";。

假设我有一个功能

std::string foo();

它返回一个潜在的大字符串,并希望将其输出传递给

int bar (const std::string& s);

考虑3种选择。

1.

const std::string my_string = foo();
const int x = bar(my_string);
std::string&& my_string = foo();
const int x = bar(my_string);
const int x = bar(foo());

我的理解是2。和3。本质上是相同的(在这两种情况下,从来没有一个l值保持my_string(,而是2。可能更具可读性(可以为字符串指定名称,并且可以将语句分解为多行(。这3个选项都不会执行任何不必要的复制。2。和1。在1.中,my_string将一直存在,直到它超出范围,而在2.中,我告诉编译器,在将其传递给bar后,我不再需要my_string,从而可以更早地释放其内存。

以上内容正确吗?如果是,则为2。在这里使用的最佳选项是什么?

我的理解是2。和3。本质上是相同的

不,实际上是1。和2。基本相同,但为3。不同。

在这两个1。和2。my_string是一个左值,因为变量的名称总是左值。在这两种情况下,字符串对象都会一直存在到作用域的末尾。在1的情况下。因为这是对象的范围,在2中。因为引用绑定的生存期延长规则。

在3。foo()是一个prvalue(一种右值(。从中具体化的临时对象以及函数的引用参数绑定到的临时对象一直存在,直到x的初始化结束。

因此,如果bar有不同的重载,采用左值引用或右值引用,在1中。和2。左值参考过载将被选择并且在3中。右值引用重载。


由于C++17,这三种方法都不会产生不必要的副本。只有一个std::string对象,在2的情况下为临时对象。和3。或者在1的情况下是命名的。


在C++17之前,在1的情况下可能有一个额外的副本。以从CCD_ 10的返回值复制到CCD_。然而,编译器被允许删除此副本,这可能在实践中几乎总是发生。由于C++17,此省略是强制性的。


在2.中,我告诉编译器,在将其传递给bar、后,我不再需要my_string

除了这适用于3.,而不是2.之外,编译器实际上根本不关心这一点,对象的生存期也不受影响。

关心的是你所传递的功能。根据惯例,如果一个函数引用了右值,这意味着该函数可以以任何适合它的方式使用和修改对象的状态。你的函数没有右值重载,所以你如何将字符串传递给它并不重要。bar永远不应该修改对象的状态。


因此,为了使区别与您的情况相关,您可以添加过载

int bar(std::string&&);

它将被调用,而不是3的第一个过载。并以某种方式利用隐含的权限将字符串对象置于未指定的状态,或者您将使用单个重载

int bar(std::string);

在这种情况下,在C++17之前,参数将通过1的复制构造函数来构造。和2。或3的move构造函数。move构造函数使用右值约定,并将重用传递的字符串的分配。或者在3.的情况下,由于C++17在此之前是确定的和可选的,因此复制/移动被省略,并且参数由函数foo()直接构造。

最新更新