0

将右值引用引入 C++ 背后的典型原因是在复杂 C++ 表达式的求值过程中消除(优化)多余的复制。

但是对于C++98/C++03有两种编译器优化技术,它们的目的基本相同,即

当上述技术(使用适当编写的代码)无法消除多余的复制,但右值引用可以成功时,是否有任何实际用例?

4

3 回答 3

15

如果您需要一个论点为什么右值引用是必不可少的,那就是unique_ptr. 它是资源管理 SBRM 类的原型。资源根据定义是不可复制的,但管理器应该是可移动的

一个拥有异构的、相关对象的容器可以直接实现为unique_ptr基类的容器,这是一个非常常见的编程习惯。在右值引用之前,永远不可能在“原生 C++”中干净地实现这一点,并且总是需要手动管理。

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

简而言之,优化只是故事的一部分,但可移动性本身就是一种品质,在右值引用之前很难解决。

于 2013-08-13T07:48:51.697 回答
4

右值引用的设计有两个目的:

  • 移动语义
  • 完美转发

移动语义

一般来说,移动语义的一个目的是优化复制。你可以复制的地方,你可以移动对象持有的资源。

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。每当您修改 a 的元素std::vector时,有时会发生一些重新分配和元素移位。在 C++98 中,元素大多在[1]周围复制,这是不必要的,因为逻辑上在单个时间点只需要对象的一种表示。使用移动语义,容器之类std::vector的可以移动对象,消除额外的副本,从而提高程序的性能。

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


完美转发

完美转发使用右值引用和特殊的模板推演规则,使我们能够实现完美的转发功能,即可以正确转发参数且无需复制的功能。

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


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


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

于 2013-08-13T07:59:10.190 回答
1

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

  • 对于大多数程序员来说,最重要(甚至是影响他们代码的唯一原因)是支持“有限副本”的可能性。(对不起,我不知道这个的好名字。)例如 iostream 类:您本身不想支持复制,但您确实希望允许在单独的函数中进行构造。一个典型的例子可能是日志流:你想从一个函数返回适当的日志流,但你只想要一个实例,它的析构函数将确保日志记录操作(即发送电子邮件、发布到 syslog 等)是原子。

  • 有时,它也可以用于优化;您提到了诸如返回值优化和复制省略之类的事情,但它们并不总是适用,尤其是在赋值时。通常,在这种情况下,您会忽略右值引用并移动,直到分析器说存在问题,但如果您正在实现一个库,您可能没有这种奢侈。

于 2013-08-13T08:34:11.143 回答