【问题标题】:Is the object returned from a function still created when it is not used?从函数返回的对象在不使用时是否仍然创建?
【发布时间】:2018-07-24 18:11:07
【问题描述】:

考虑以下代码。当 doStuff() 被调用但没有使用返回值时会发生什么? SomeClass 是否仍然创建?当然,创建本身可能会产生重要的副作用,但是复制构造函数也可以,并且它们在 RVO / 复制省略中仍然被省略。

SomeClass doStuff(){
    //...do stuff
    return SomeClass( /**/);
}

SomeClass some_object = doStuff();
doStuff(); //What happens here?

(编辑:用 GCC -O3 对此进行了测试。对象被构造然后立即销毁)

【问题讨论】:

  • 函数返回的对象在超出范围之前将保持有效。这意味着调用函数的封闭范围,然后它超出范围将调用任何对象析构函数。在您的示例中,您没有为第二次调用分配任何内容,但同样适用。
  • @SPlatten 这不是真的。在示例中,为 some_class 调用了复制构造函数,但 RVO 除外。返回的对象遵循相同的规则,因为它是一个临时对象,并且会在语句之后立即销毁
  • 我会这样看:假设该函数位于已编译的第三方库中。一旦在客户端代码中调用,编译器如何提前知道返回的对象是否会被忽略?现在,根据定义是否可访问,让函数表现不同是否是可取的/明智的?好吧,这并不能真正回答你的问题,但我认为这对标准中的决定提供了很大的暗示。
  • 我会说它有点像{SomeClass s{};}(构造函数和析构函数必须被调用(至少好像))
  • @PasserBy,对象的范围和生命周期规则适用于复制或以其他方式复制的所有对象。如果对象不是全局的并且是在范围内创建的,那么它将在超出范围时被销毁(它的析构函数)。

标签: c++ lifetime


【解决方案1】:

我觉得对 RVO 和复制省略存在误解。这并不意味着没有创建函数的返回值。它总是被创建的,这不是实现可以逃避的事情。

在删除副本方面,唯一的余地是削减中间人,尽管有副作用。当您使用调用结果初始化对象时,标准允许插入目标对象,以便函数直接初始化。

如果您不提供目标对象(通过使用结果),则必须将临时对象物化并销毁,作为包含函数调用的完整表达式的一部分。

所以用你的例子来玩一下:

doStuff(); // An object is created and destroyed as part of temporary materialization
           // Depending on the compilers analysis under the as-if rule, there may be
           // further optimization which gets rid of it all. But there is an object there 
           // formally.

std::rand() && (doStuff(), std::rand());
// Depending on the result of std::rand(), this may or may not create an object.
// If the left sub-expression evaluates to a falsy value, no result object is materialized.
// Otherwise, one is materialized before the second call to std::rand() and 
// destroyed after it.

【讨论】:

  • @Yola - 如果没有可观察到的行为受到影响,它肯定会受到影响。取决于正在创建的对象。但这不是关于复制省略,而是关于在 as-if 规则下进行优化。从抽象 C++ 机器的角度来看,那里有一个对象。
  • 您说“标准允许插入目标对象”(强调“允许”)。它真的是“允许”还是“必需”?或者根据情况允许/要求?
  • @Caninonos - 这是一种非常常见的优化,如果编译器想要相关,它可以更好地实现。 But the standard only says an implementation is allowed,没有义务。还值得注意的是,RVO 是一种不同形式的优化,与 C++17 要求的保证复制省略没有直接关系。
  • @Dai:记住,C++ 源代码描述了一个程序。它不是要在计算机上执行的指令序列。这就是编译程序集的用途。人们有时将其称为“优化”,但实际上这只是语言 的基本性质。确实,您的编译器可以根据“优化级别”设置生成或多或少高效(和模糊)的代码,但这与 C++ 是您想要的 抽象表示 无关电脑做。
  • @StoryTeller:没问题。我拿 PayPal ;)
【解决方案2】:

编译器在某些情况下可能会忽略不必要的副本,即使它有副作用,是的。

编译器可能不会忽略一个对象的整个存在,如果它有副作用的话。

如果它没有副作用,那么就无法观察到任何结果,因此存在与否实际上是一个非问题。

tl;dr:标准列出了非常具体的省略机会,这不是其中之一。

【讨论】:

  • 副作用是一种有趣的观察方式。假设构造函数改变了一个全局变量的状态(没有什么比一个好的代码味道更好的了),那么这个变量需要改变它的状态,即使这个对象没有在任何地方被引用。
  • @PierreArlaud:这是一个副作用。 C++ 标准和计算机科学理论一致使用术语“副作用”。
  • 我并没有不同意你的观点,我觉得你的回答很有趣,因为它让我想起了我刚刚给你的例子
  • @PierreArlaud:啊。我以为你的意思是,你知道,“有趣”。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-06-29
  • 2016-01-30
  • 2012-11-06
  • 1970-01-01
  • 2015-02-10
  • 1970-01-01
  • 2017-09-24
相关资源
最近更新 更多