【问题标题】:Can libraries with implementation seperated from decalration benefit from RVO/NRVO? [closed]实现与声明分离的库能否从 RVO/NRVO 中受益? [关闭]
【发布时间】:2017-11-24 17:40:57
【问题描述】:

正如标题所示,我正在编写一个带有类模板和几个非模板运算符重载的静态库。类模板在a.h中定义,函数在a.cc中定义。

所以我决定继续询问 RVO/NRVO 是否可以使库的用户代码受益?

编辑: 对此我很抱歉,这是我刚刚提出的另一个问题,不应该被问到这个问题中。 为了让场景更清晰,我其实是在尝试封装 uint8_t 之类的类型,并打算自己写一些大的整数类型。

【问题讨论】:

  • 声明和定义的分离与RVO/NRVO有什么关系??这是与 constexpr 完全不同的上下文,它需要在编译时(而不是链接器阶段)可见。
  • 我想知道当定义与声明分开时,RVO/NRVO 是否仍然会发生。
  • 当然,为什么不呢??
  • 哦,那怎么会这样呢?编译器是如何做到这一点的?
  • 也许this article 有助于理解。它是在相关代码的定义中实现的,而不是在调用代码中实现的。看起来普通的Wikipedia article 也提供了一些关于这个疑问的信息。

标签: c++ c++14 static-libraries rvo nrvo


【解决方案1】:

当函数返回纯右值时,这意味着(C++17 之前)返回值是一个临时对象。所以,这个临时对象发生了两件事:

  1. 它必须由创建它的函数初始化。
  2. 调用创建者的函数必须以某种方式使用它(即使它刚刚被丢弃,技术上仍在使用它)。

按照标准,如果您执行return Type(...);,则返回表达式被求值,产生一个临时值,该临时值用于复制初始化返回值对象。

但是,标准规定如果Type 与返回的纯右值对象的类型相同,则不必进行此复制初始化。在这种情况下,编译器将简单地将临时的初始化直接应用于返回值对象。

按照标准,如果你做Type var = some_func(...),并且some_func返回一个值,那么some_func返回的临时值将用于复制初始化var

但是,标准规定如果Typesome_func 返回的值类型,则不必进行复制初始化。因此,some_func初始化的返回值对象是var本身。不是一些初始化var的临时对象; some_func直接初始化var

这两个过程完全相互独立。

返回值初始化的省略是基于函数的实现。该实现不关心调用者在做什么。它只是直接初始化返回值对象,而不是从返回表达式中复制。

从函数的返回值中省略变量的初始化不关心函数的实现。它仅基于函数返回一个纯右值,并且该纯右值的类型与由它初始化的对象的类型相同的事实。它只需要查看函数的声明即可完成省略操作的部分。

当这两种情况都发生时,您将完全删除从函数内部到其最终目的地的所有副本。但两者都不需要另一个存在。

所以,考虑以下几点:

Type foo()
{
  Type t;
  return t;
}

T t2 = foo();

按照标准,这是两次复制初始化。首先,foo 的返回值是通过从t 移动来初始化的。其次,t2是通过从foo的返回值移动来初始化的。

如果编译器可以忽略这两个,那么你得到 0 步。如果编译器可以省略 t2 的初始化但不能对 t 执行 NRVO,那么你会得到 1 move`。如果它也做不到,那么你会得到 2 步(你应该立即停止使用那个编译器;))。


如果您想要更多的实现细节,那么它与函数调用约定和 ABI 有关。

函数参数的存储空间由调用者分配。因此,调用者看到该函数将返回一个纯右值,因此它分配了足够的适当对齐方式的存储空间来存储该值,然后使用指向该存储空间的指针调用该函数。函数的实现将在初始化返回值时使用该存储。

省略,在函数实现方面,只是直接在返回值内存中构造对象。省略,在使用返回值的函数方面,只是简单地传递将使用的对象的存储空间。上面的t2 示例将通过将t2 的存储作为返回值存储传递给foo 来执行省略。

foo 的编译器不需要知道或关心返回值存储是命名值还是临时值。它所知道的只是它已经被赋予了构造返回值的存储空间。

foo 的调用者的编译器只需要函数签名,因为它告诉它执行这种省略所需的一切。

【讨论】:

  • @JiaHaoXu 这就是你要找的吗?或者是如何实现这些东西,例如编译器/链接器如何知道将值放在哪里?
  • 感谢您回答我的问题,但我仍有一些问题。你的意思是,调用者只是给被调用者一个return_val的地址(如果它与函数返回的类型相同),函数是否执行RVO/NRVO与调用者无关,只是与被调用者?
  • @JiaHaoXu:我的意思就是我写的。调用者将存储传递给被调用者以进行初始化。就是这么简单。被调用者如何初始化是被调用者的事情。
  • @Passer By 是的,我很好奇编译器是如何做到的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-18
  • 2018-08-03
  • 2012-05-18
  • 2010-12-10
相关资源
最近更新 更多