【问题标题】:Is it safe to modify RVO values within an RAII construct? [duplicate]在 RAII 构造中修改 RVO 值是否安全? [复制]
【发布时间】:2020-05-15 10:56:27
【问题描述】:

考虑以下程序:

#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++ destructor raii copy-elision rvo


【解决方案1】:

简答:由于 NRVO,程序的输出可能是 35。两者都有效。


背景请先看:

指南:

  • 避免使用修改返回值的析构函数。

例如,当我们看到以下模式时:

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

我们需要问自己:A::~A() 是否会以某种方式修改我们的返回值?如果是,那么我们的程序很可能有错误。

例如:

  • 在销毁时打印返回值的类型就可以了。
  • 在销毁时计算返回值的类型不好

[来自https://stackoverflow.com/a/54566080/9305398]

【讨论】:

    【解决方案2】:

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

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

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

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

    但如果RvoObjunique_ptr&lt;T&gt;,那么你就有麻烦了。为什么?因为返回值的移动操作发生在调用局部变量的析构函数之前。所以在这种情况下,obj 将处于已移出状态,这对于 unique_ptr 意味着它是空的。

    这很糟糕。

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

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


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

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

    【讨论】:

    • 太棒了,谢谢! “有一个潜在的问题,因为你的代码会根据是否发生省略而表现不同”——所以我可以假设代码在 C++17 中总是安全的?
    • 我不认为真正的问题只是“这会调用 UB 吗?”还有“这可以保证打印5吗?”我认为@OP 假设如果它不是 UB,则该值不会随编译器的一时兴起而改变,但事实并非如此。坚持标准,不保证 NVRO,所以我相信这段代码可以打印 either 35。保证不是UB,也不是完全确定。
    • @BrianRodriguez:你的帖子被标记为 c++17,所以我相对于它的标签来回答它。
    • @BrianRodriguez 如果您的意思是“这段代码是否总是在 C++17 中打印 5?”那是不。 C++17 中保证的那种省略不是你需要的那种省略。
    • @HTNW 你能详细说明一下吗(也许在答案中)?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-21
    • 1970-01-01
    • 2012-11-13
    • 2016-11-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多