58

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?

Alex Bitek
  • 6,529
  • 5
  • 47
  • 77
Clinton
  • 22,361
  • 15
  • 67
  • 163
  • 22
    (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. – Steve Jessop Jun 04 '11 at 00:41
  • 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. – Johannes Schaub - litb Jun 04 '11 at 08:57

1 Answers1

63

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.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • 48
    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. – Howard Hinnant Jun 04 '11 at 03:31
  • I have a similar sample class to figure this bit out! Except I use breakPoints and printfs. – amritanshu Oct 29 '20 at 12:22
  • Side note: Actually we cannot legally print `A is destructed` without undefined behaviour ;) – Aconcagua Jun 21 '22 at 10:36
  • Agreed. Works best with optimizations turned off. – Howard Hinnant Jun 21 '22 at 14:45