在RAII结构中修改RVO值是否安全? [重复]

问题描述 投票:5回答:2

考虑以下程序:

#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
2个回答
2
投票

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


有关背景,请首先参阅:

指南:

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

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

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

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

例如:

  • 在销毁时打印返回值的类型很好。
  • 计算破坏时的返回值的类型很好,[[not。

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

]

3
投票
复制省略仅允许实现消除由函数生成的对象的存在。即,它可以删除从objfoo的返回值对象和obj的析构函数的副本。但是,该实现无法更改其他任何内容。

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

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

[然而,有一个问题。请参见return <variable_name>;,以获取调用move操作的局部变量。在您的情况下,从RvoObj中移动与从中复制相同。因此,对于您的特定代码,就可以了。

但是如果RvoObj例如是unique_ptr<T>,您会遇到麻烦。为什么?因为对返回值的移动操作发生

在调用局部变量的析构函数之前

。因此,在这种情况下,obj将处于移出状态,对于unique_ptr而言,意味着它为空。不好。

如果放弃此举,那就没有问题。但是由于不需要省略,因此存在潜在的问题,因为代码会根据是否发生省略而表现不同。这是实现定义的。

因此,一般而言,最好不要让析构函数依赖于要返回的局部变量的存在。


以上内容纯粹与您有关

未定义行为

的问题有关。根据是否发生省略来更改行为不是UB。该标准定义一个或另一个将发生。但是,您不能也不应该

依靠它

© www.soinside.com 2019 - 2024. All rights reserved.