编译器优化

11

我有一个问题要问你。 :) 你能告诉我以下代码应该产生的输出吗?

#include <iostream>
struct Optimized
{
    Optimized() { std::cout << "ctor" << std::endl; }
    ~Optimized() { std::cout << "dtor" << std::endl; }
    Optimized(const Optimized& copy) { std::cout << "copy ctor" << std::endl; }
    Optimized(Optimized&& move) { std::cout << "move ctor" << std::endl; }
    const Optimized& operator=(const Optimized& rhs) { std::cout << "assignment operator" << std::endl; return *this; }
    Optimized& operator=(Optimized&& lhs) { std::cout << "move assignment operator" << std::endl; return *this; }
};

Optimized TestFunction()
{
    Optimized a;
    Optimized b = a;
    return b;
}

int main(int argc, char* argv[])
{
    Optimized test = TestFunction();
    return 0;
}

我的第一反应是:

  1. 构造函数
  2. 拷贝构造函数
  3. 移动构造函数
  4. 析构函数
  5. 析构函数
  6. 析构函数

这是正确的,但只有在关闭编译器优化时才是如此。当开启优化时,输出完全不同。开启优化后,输出结果为:

  1. 构造函数
  2. 拷贝构造函数
  3. 析构函数
  4. 析构函数

通过编译器优化,测试变量成为返回变量。

我的问题是,什么条件会导致不能按这种方式进行优化?

我一直被教导,返回一个需要额外复制构造函数的结构/类可以通过作为引用传递来更好地进行优化,但是编译器正在为我完成这项工作。因此,返回结构是否仍被认为是不好的形式?


它被称为返回值优化。 - Dan
重新格式化您的问题以更符合此格式将会很有帮助:这是我的代码“”,这是我的结果。是否有任何方法可以得到“”? - N_A
1
@mydogisbox:实际上,我觉得这个问题很清晰明了。 - Matthieu M.
但是,@Mydogisbox,它已经以那种格式呈现了。它展示了代码。它展示了预期结果和实际结果。然后它询问产生预期结果所必需的条件。 - Rob Kennedy
@Rob,我已经为示例代码添加了必要的包含文件。对此很抱歉...这应该是:+1 为好问题,-1 为不可移植,+1 为纠正问题。 :) - BabelFish
显示剩余3条评论
4个回答

14

这被称为复制省略,它是一种特殊的处理方式,而不是复制/移动。

只要可以复制/移动(即该方法已声明并可访问),标准就明确允许进行优化。

编译器中的实现通常在这种情况下被称为返回值优化。有两个变体:

  • RVO:当您返回临时对象时(return "aa" + someString;
  • NRVO:N表示命名,当您返回具有名称的对象时

所有主要编译器都实现了这两种变体,但后者可能仅在更高的优化级别下才会启用,因为其更难以检测。

因此,回答你关于返回结构体的问题:我建议这么做。考虑以下因素:

// Bad
Foo foo;
bar(foo);

-- foo can be modified here


// Good
Foo const foo = bar();

后者不仅更清晰,还可以实现 const 强制!


1
我从未见过它被称为Unnamed RVO。通常只称为RVO。RVO和NRVO。 - Puppy
@DeadMG:没错,我经常希望它是RVO = URVO + NRVO,因为在这里RVO既代表通用的又代表特定的...已更正以匹配使用方式。 - Matthieu M.

4
两种输出都是合法的。C++03语言标准在12.8/15条款中表示:
当满足某些条件时,即使对象的复制构造函数和/或析构函数具有副作用,实现也可以省略类对象的复制构造, 在这种情况下,实现将省略的复制操作的源和目标视为仅仅是引用同一对象的两种不同方式, 并且该对象的销毁发生在没有优化的情况下两个对象将要被销毁的时间之后。111) 可以在以下情况下允许省略复制操作(可以组合使用以消除多次复制):
  • 在返回类返回类型的函数中,当表达式是与函数返回类型相同的非易失性自动对象的名称时,可以通过直接在函数的返回值中构造自动对象来省略复制操作。
  • 当一个尚未绑定到引用(12.2)的临时类对象将被复制到具有相同cv-unqualified类型的类对象时,可以通过直接在省略复制的目标中构造临时对象来省略复制操作。

1
+1 即使对象的复制构造函数和/或析构函数有副作用,也不知道这一点。这似乎可能会引发问题... - Mooing Duck

2
这段代码的输出是不可预测的,因为语言规范明确允许省略“不必要”的类对象临时副本,即使它们的复制构造函数有副作用。
是否发生这种情况可能取决于许多因素,包括编译器优化设置。
在我看来,将上述复制省略称为“优化”并不完全正确(尽管希望在此处使用此术语是完全可以理解的,并且它被广泛用于此目的)。我会说,“优化”这个术语应该保留给当编译器偏离“抽象C++机器”的行为而又保留了程序的可观察行为的情况。换句话说,真正的优化意味着违反语言规范的抽象要求。由于在这种情况下没有违反(复制省略在标准中明确允许),因此不存在真正的“优化”。我们在这里观察到的只是C++语言在其抽象层面上的工作方式。根本不需要涉及“优化”概念。

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接