【问题标题】:How does the compiler determine when is it safe to RVO?编译器如何确定 RVO 何时安全?
【发布时间】:2013-12-17 12:50:06
【问题描述】:

编译器如何确定何时对 RVO 是安全的?不,我不是指右值,而是左值——如果我理解正确的话,RVO 通过将目标地址“转发”到方法来工作,所以它返回到目标地址而不是临时地址,然后复制/分配给目标。

但是编译器如何知道执行 RVO 是安全的?如果左值中已经有一些数据,包括动态分配的资源怎么办?在这种情况下,RVO 可能会导致资源泄漏。也许有一些规则指定它是否适用于执行优化或坚持使用复制或分配?

【问题讨论】:

  • RVO 不会导致资源泄露(对于理智的类型)。该对象是在被调用者期望返回值所在的同一位置构造的。确切的规则可能取决于实施,但标准并未强制执行。
  • 怎么会导致资源泄露?它的行为就像您将要复制到的变量作为参考参数传递一样
  • 如果它是一个“构造函数”类型的函数,它为成员指针分配动态资源,在同一个对象的地址上运行该函数两次将分配资源两次,我假设一个“构造函数类型" 函数将直接分配而不是检查指针是否已经分配。这对于“构造函数类型”函数是有意义的。所以第一个资源会泄露...
  • 构造函数为什么会被调用两次?
  • @sftrabbit - 好吧,我明确地将它放在 "" 中,并将其称为“构造函数”类型的函数,而不是构造函数本身。假设 ` T a = foo1(); .... a = foo2()` - foo1 应用 RVO 是安全的,但如果 foo2 应用它,动态资源将泄漏。

标签: c++ compiler-construction return-value-optimization


【解决方案1】:

RVO 只能初始化一个新对象,不能重新分配现有对象。所以在这种情况下:

Thing thing = make_thing();

thing 的地址被传递给函数,该函数就地初始化它。

在这种情况下:

thing = make_thing();

RVO(通常)创建一个临时文件,然后将其分配给thing。只要类型可正确分配,就不会有泄漏或类似问题。由于它是一个临时的 rvalue,因此可以从中移动,这可能比复制更有效。如果类型是可简单赋值的,那么这个赋值也可以省略——编译器在选择如何调用函数时会知道是否是这种情况。

【讨论】:

  • 所以,有一条规则“rvo 只能应用于“新”对象”,但是编译器如何确定对象何时是新对象?如果它只是实例化而没有别的?这是否意味着,如果在实例化和 rvo 点之间使用对象,即使在技术上不妨碍 rvo 的操作也会禁用优化?
  • @user2341104:何时可以省略复制和移动的规则相当复杂,但这是一个合理的简化。编译器可以看到该对象是新的,因为函数调用是其初始化程序的一部分。如果它被重新分配(在我的第二个示例中),那么(通常)RVO 用于初始化一个临时分配,正如我所描述的那样。
  • 那么,T t = foo() 将使用 RVO,但 T t; t = foo() 不会(在这两个语句之间没有其他任何事情发生)?换句话说,编译器是否需要将赋值作为实例化的一部分,或者它是否有其他方法来确定赋值是否是在实例化点之后调用的第一个方法?
  • @user2341104:第一种情况不是赋值,而是初始化。 RVO 可用于初始化,但不能(通常)用于赋值。
  • RVO 是什么似乎有些混淆。 RVO 独立于调用者正在做什么;它发生在执行返回的函数中。 (其他优化,如初始化部分中描述的优化,确实适用。)
【解决方案2】:

返回值优化是复制省略的一种特殊情况。标准描述的以下情况可能会发生:

在具有类返回类型的函数的 return 语句中,当表达式是与函数具有相同 cv- 非限定类型的非易失性自动对象(函数或 catch 子句参数除外)的名称时返回类型,直接在函数的返回值中构造自动对象即可省略复制/移动操作

这没有理由导致内存泄漏。如果类在其构造函数中执行了一些动态分配,当对象被直接构造为函数的返回值时,就会发生这种情况。


响应您的评论(foo1foo2 都构造 T 对象并返回它们):

T a = foo1();
a = foo2();

我们在这里不仅关注 RVO,还关注另一种在尝试从临时对象构造对象时发生的复制省略。

在第一行,可以省略两个副本/移动:

  1. foo1返回构造的对象
  2. 将返回的对象复制到a

foo1中构造的对象可以直接在a的位置创建。如果构造函数动态分配某个对象,则只会为a 对象分配一次。

在第二行中,可以省略单个复制/移动 - 仅从函数返回。所以foo2构造的对象会直接在函数的返回值中创建,然后将其复制/移动赋值a中。不会省略复制/移动分配。

然后由复制/移动分配操作员来确保原始分配的资源被安全丢弃,唯一剩余的资源是在foo2 中创建的资源。

【讨论】:

  • @user2341104 但是只构造一次,直接进入函数的返回值。
  • 查看我对该问题的评论
  • @user2341104 回复我的回答。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-11-05
  • 1970-01-01
  • 1970-01-01
  • 2020-04-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多