首页 > 解决方案 > 在 RAII 构造中修改 RVO 值是否安全?

问题描述

考虑以下程序:

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

clang 和 gcc ( demo )都5在不调用复制或移动构造函数的情况下输出。

这种行为是否得到 C++17 标准的良好定义和保证?

标签: c++destructorraiicopy-elisionrvo

解决方案


复制省略仅允许实现删除由函数生成的对象的存在。也就是说,它可以删除 的副本obj到 的返回值对象foo和 的析构函数obj。但是,实施不能改变任何其他东西。

返回值的复制将在调用函数中本地对象的析构函数之前发生。并且 的析构函数obj会在 的析构函数之后发生run,因为自动变量的析构函数是按其构造的相反顺序执行的。

这意味着在其析构函数run中访问是安全的。obj表示的对象是否在完成obj后被销毁run并不会改变这一事实。

但是,有一个问题。看,调用移动操作return <variable_name>;需要一个局部变量在您的情况下,移动与从中复制相同。因此,对于您的特定代码,它会很好。RvoObj

但是,如果RvoObj是,例如,unique_ptr<T>你就会遇到麻烦。为什么?因为对返回值的移动操作发生在调用局部变量的析构函数之前。所以在这种情况下obj将处于从状态移出,这unique_ptr意味着它是空的。

那很糟。

如果移动被省略,那么就没有问题。但是由于不需要省略,因此可能存在问题,因为您的代码将根据是否发生省略而表现不同。这是实现定义的。

所以一般来说,最好不要让析构函数依赖于你返回的局部变量的存在。


以上纯粹与您关于未定义行为的问题有关。根据省略是否发生来改变行为不是UB。该标准定义了其中一种会发生。

但是,您不能也不应该依赖它


推荐阅读