Move or Named Return Value Optimization (NRVO)?
Asked Answered
C

1

61

Lets say we have the following code:

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

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

It seems the compiler has two approaches here:

(a) NRVO: Destruct x, then construct f() in place of x.
(b) Move: Construct f() in temp space, move f() into x, destruct f().

Is the compiler free to use either approach, according to the standard?

Ctn answered 4/6, 2011 at 0:31 Comment(2)
(a) isn't allowed. Aside from the fact that an assignment operator must be called, it would have the wrong behavior when some part of the ... in f throws an exception. x should not be changed in that case, so if it's already been destructed that's a problem.Vouge
That's a problem with vague questions. I thought that he didn't mean what he literally wrote. Apparently some other people thought so too.Paleontology
G
65

The compiler may NRVO into a temp space, or move construct into a temp space. From there it will move assign x.

Update:

Any time you're tempted to optimize with rvalue references, and you're not positive of the results, create yourself an example class that keeps track of its state:

  • constructed
  • default constructed
  • moved from
  • destructed

And run that class through your test. For example:

#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;
}

If it helps, put print statements in the special members that you're interested in (e.g. copy constructor, move constructor, etc.).

Btw, if this segfaults on you, don't worry. It segfaults for me too. Thus this particular design (returning an rvalue reference to a local variable) is not a good design. On your system, instead of segfaulting, it may print out "A is destructed". This would be another sign that you don't want to do this.

Gaygaya answered 4/6, 2011 at 0:36 Comment(4)
I purposefully used the OP's terminology in my answer. The standard strives to be precise, but is a lousy tutorial. I did not (and still do not) recognize that I was being vague. This is of course a common failing to which I am not immune. I'll gladly clarify if I knew which parts of my answer were ambiguous. My goal is to spread knowledge, not make it confusing.Gaygaya
I have a similar sample class to figure this bit out! Except I use breakPoints and printfs.Syrinx
Side note: Actually we cannot legally print A is destructed without undefined behaviour ;)Electrojet
Agreed. Works best with optimizations turned off.Gaygaya

© 2022 - 2024 — McMap. All rights reserved.