引入右值引用真的有用吗

Is the introduction of rvalue references actually useful?

本文关键字:真的 有用 引用      更新时间:2023-10-16

在C++中引入右值引用背后的典型推理是在评估复杂C++表达式期间消除(优化)多余的复制。

然而,对于C++98/C++03,有两种编译器优化技术,它们基本上起到了相同的作用,即

  • 返回值优化
  • 复制省略

当上述技术(使用适当编写的代码)无法消除多余的复制,但重新值引用可以成功时,有没有真实的用例?

如果您需要一个单独的参数来解释为什么右值引用是必不可少的,那么它就是unique_ptr。它是资源管理SBRM类的原型。根据定义,资源是不可复制的,但管理者应该可移动

拥有异构相关对象的容器可以直接实现为基类的unique_ptrs的容器,这是一种非常常见的编程习惯用法。在重新值引用之前,在"本机C++"中永远不可能干净地实现这一点,并且总是需要手动内务处理。

线程和锁是资源的进一步例子。

简言之,优化只是故事的一部分,但可移动性本身就是一种质量,在重新值引用之前,它不太容易解决。

右值引用的设计考虑到两个目标:

  • 移动语义
  • 完美转发

移动语义

一般来说,移动语义的一个目的是优化复制。在可以复制的地方,只需移动对象所拥有的资源即可。

std::string str("The quick brown fox jumps over the lazy dog.");
std::cout << str;      // Do something
func(std::move(str));  // You do not need str in the subsequent lines, and so move

此外,编译器可以优化函数的返回值,其中RVO可能不适用

std::vector<std::string> read_lines_from_file(const std::string& filename);
auto lines = read_lines_from_file("file1.txt");  // Can possibly be optimized with RVO
// Do something with lines
lines = read_lines_from_file("file2.txt");  // RVO isn't applicable, but move is
// Do something with lines

如果您在类中实现了移动语义,那么当您将它们放入容器中时,您很可能会体验到显著的性能提升。考虑std::vector。每当您修改std::vector的元素时,在某个时间,就会发生一些重新定位和元素移位。在C++98中,元素大多在[1]周围复制,这是不必要的,因为在逻辑上,在一个时间点上只需要一个对象的表示。使用移动语义,像std::vector这样的容器可以移动对象,消除额外的副本,从而提高程序的性能。

移动语义的一个非常重要的用途不仅用于优化,而且用于实现唯一的所有权语义。一个很好的例子是std::unique_ptr,其中它只能四处移动(不能复制),因此(通过正确使用)可以保证只有一个std::unique_ptr在管理所包含的指针或资源。


完美的转发

完美转发使用右值引用和特殊的模板推导规则,使我们能够实现完美的转发函数,即可以正确转发参数而不进行任何复制的函数。

例如std::make_shared和即将推出的std::make_unique。还有容器的新emplace*方法,你会发现它非常有用和方便(更不用说它也会非常高效)。


这个答案提供了关于移动语义和完美转发的右值引用的非常好和彻底的解释。


[1]我从Bjarne Stroustrup的一次演讲中听到的一件事是容器通常实现一些特定的移动语义。因此,正如他所说,你可能不会经历那么多的加速。

使用右值引用和move有两个原因语义。

  • 对于大多数程序员来说,最重要的(甚至是唯一的)影响其代码的原因)支持"限量版"。(对不起,我不知道一个好名字)就像iostream类一样,例如:希望支持复制,但您确实希望允许在单独的功能中构建。一个典型的例子可能是日志流:您希望从一个函数,但您只需要一个实例,其析构函数将确保日志操作(即发送电子邮件、发布到syslog等)是原子的。

  • 有时,它也可以用于优化;你提到比如返回值优化和复制省略,但它们不要总是申请,尤其是分配任务。一般来说,在在这种情况下,您将忽略右值引用并移动到探查器说有问题,但如果你实现一个库,你可能没有这种奢侈。