搬移还是命名返回值优化(NRVO)?

58

假设我们有以下代码:

std::vector<int> f()
{
  std::vector<int> y;
  ...
  return y;
} 

std::vector<int> x = ...
x = f();

编译器似乎有两种方法:

(a) NRVO(命名值返回优化):先销毁x,然后在x的位置上构造f()。
(b) 移动语义:在临时空间中构造f(),将f()移动到x中,然后销毁f()。

根据标准,编译器是否可以自由选择使用其中任一种方法?


22
(a) 不被允许。除了分配运算符“必须”被调用外,当 f 中的某些部分抛出异常时,它将产生错误的行为。在这种情况下,不应更改 x 的值,因此如果已经被销毁,则会出现问题。 - Steve Jessop
这是一个模糊问题。我认为他并不是字面意思所写的意思。显然其他人也这样认为。 - Johannes Schaub - litb
1个回答

63

编译器可能会将NRVO(命名返回值优化)到一个临时空间,或者将对象移动构造到一个临时空间。然后将其移动赋值给x

更新:

任何时候当您尝试使用右值引用进行优化,并且不确定结果时,请创建一个跟踪其状态的示例类:

  • 已构造
  • 默认构造
  • 已移动
  • 已析构

并通过您的测试运行该类。例如:

#include <iostream>
#include <cassert>

class A
{
    int state_;
public:
    enum {destructed = -2, moved_from, default_constructed};

    A() : state_(default_constructed) {}
    A(const A& a) : state_(a.state_) {}
    A& operator=(const A& a) {state_ = a.state_; return *this;}
    A(A&& a) : state_(a.state_) {a.state_ = moved_from;}
    A& operator=(A&& a)
        {state_ = a.state_; a.state_ = moved_from; return *this;}
    ~A() {state_ = destructed;}

    explicit A(int s) : state_(s) {assert(state_ > default_constructed);}

    friend
    std::ostream&
    operator<<(std::ostream& os, const A& a)
    {
        switch (a.state_)
        {
        case A::destructed:
            os << "A is destructed\n";
            break;
        case A::moved_from:
            os << "A is moved from\n";
            break;
        case A::default_constructed:
            os << "A is default constructed\n";
            break;
        default:
            os << "A = " << a.state_ << '\n';
            break;
        }
        return os;
    }

    friend bool operator==(const A& x, const A& y)
        {return x.state_ == y.state_;}
    friend bool operator<(const A& x, const A& y)
        {return x.state_ < y.state_;}
};

A&& f()
{
    A y;
    return std::move(y);
}

int main()
{
    A a = f();
    std::cout << a;
}

如果有帮助的话,可以在你感兴趣的特殊成员函数中放置打印语句(例如复制构造函数、移动构造函数等)。

顺便说一下,如果这个程序导致了段错误,请不要担心。它也会在我的电脑上导致段错误。因此,这种返回对局部变量的右值引用的设计是不好的。在你的系统上,它可能会输出“已析构A”这样的内容。这是另一个不想要的标志。


48
我在回答中故意使用了提问者的术语。标准努力保持精确,但却不是一个好的教程。我没有(现在仍然没有)意识到自己的表述含糊不清。这当然是我所不能免的常见缺点。如果我知道我的回答哪些部分含糊不清,我很乐意进行澄清。我的目标是传播知识,而不是让它变得令人困惑。 - Howard Hinnant
我有一个类似的示例类来解决这个问题!除了我使用断点和打印语句。 - amritanshu
旁注:实际上,我们不能在没有未定义行为的情况下打印“ A已被销毁”;) - Aconcagua
同意。最好关闭优化以获得最佳效果。 - Howard Hinnant

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