c++ - 在 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 标准的良好定义和保证?
解决方案
复制省略仅允许实现删除由函数生成的对象的存在。也就是说,它可以删除 的副本obj
到 的返回值对象foo
和 的析构函数obj
。但是,实施不能改变任何其他东西。
返回值的复制将在调用函数中本地对象的析构函数之前发生。并且 的析构函数obj
会在 的析构函数之后发生run
,因为自动变量的析构函数是按其构造的相反顺序执行的。
这意味着在其析构函数run
中访问是安全的。obj
表示的对象是否在完成obj
后被销毁run
并不会改变这一事实。
但是,有一个问题。看,调用移动操作return <variable_name>;
需要一个局部变量在您的情况下,移动与从中复制相同。因此,对于您的特定代码,它会很好。RvoObj
但是,如果RvoObj
是,例如,unique_ptr<T>
你就会遇到麻烦。为什么?因为对返回值的移动操作发生在调用局部变量的析构函数之前。所以在这种情况下obj
将处于从状态移出,这unique_ptr
意味着它是空的。
那很糟。
如果移动被省略,那么就没有问题。但是由于不需要省略,因此可能存在问题,因为您的代码将根据是否发生省略而表现不同。这是实现定义的。
所以一般来说,最好不要让析构函数依赖于你返回的局部变量的存在。
以上纯粹与您关于未定义行为的问题有关。根据省略是否发生来改变行为不是UB。该标准定义了其中一种会发生。
但是,您不能也不应该依赖它。
推荐阅读
- java - 单选按钮应打开新活动,仅当 edittext 字段不为空时
- node.js - ask-smapi-sdk 身份验证失败 error_description:'客户端身份验证失败',错误:'invalid_client'
- sqlite - sqlite3 在用户函数上返回语法错误
- java - 我怎样才能让它运行?
- python-3.x - 通过减少 if else 语句的数量来优化代码
- python - 如何加入存储在按多索引排序的字典列表中的不同熊猫数据帧
- laravel - 通过 eloquent 具有子项和权限的 Laravel 菜单
- javascript - 使用 JavaScript 调用 API 期间“CORS 标头‘Access-Control-Allow-Origin’缺失”
- java - 如何将嵌套的 JSON 对象转换为数组 Spring Boot?
- java - 如何在单个 XML 标记中插入换行符?