c++destructorraiicopy-elisionrvo

Is it safe to modify RVO values within an RAII construct?


Consider the following program:

#include <functional>
#include <iostream>

class RvoObj {
  public:
    RvoObj(int x) : x_{x} {}
    RvoObj(const RvoObj& obj) : x_{obj.x_} { std::cout << "copied\n"; }
    RvoObj(RvoObj&& obj) : x_{obj.x_} { std::cout << "moved\n"; }

    int x() const { return x_; }
    void set_x(int x) { x_ = x; }

  private:
    int x_;
};

class Finally {
  public:
    Finally(std::function<void()> f) : f_{f} {}
    ~Finally() { f_(); }

  private:
    std::function<void()> f_;
};

RvoObj BuildRvoObj() {
    RvoObj obj{3};
    Finally run{[&obj]() { obj.set_x(5); }};
    return obj;
}

int main() {
    auto obj = BuildRvoObj();
    std::cout << obj.x() << '\n';
    return 0;
}

Both clang and gcc (demo) output 5 without invoking the copy or move constructors.

Is this behavior well-defined and guaranteed by the C++17 standard?


Solution

  • Short answer: due to NRVO, the output of the program may be either 3 or 5. Both are valid.


    For background, see first:

    Guideline:

    For example, when we see the following pattern:

    T f() {
        T ret;
        A a(ret);   // or similar
        return ret;
    }
    

    We need to ask ourselves: does A::~A() modify our return value somehow? If yes, then our program most likely has a bug.

    For example:

    [From https://stackoverflow.com/a/54566080/9305398 ]