【发布时间】:2023-03-16 04:40:01
【问题描述】:
示例:
我有以下代码(简化为模型示例,使用 Qt 库,Qt 类行为解释如下):
struct Test_impl {
int x;
Test_impl() : x(0) {}
Test_impl(int val) : x(val) {}
};
class Test {
QSharedPointer<Test_impl> m;
public:
Test() : m(new Test_impl()) {}
Test(int val) : m(new Test_impl(val)) {}
void assign(const QVariant& v) {m = v.value<Test>().m; ++m->x;}
~Test(){--m->x;}
};
Q_DECLARE_METATYPE(Test)
QSharedPointer 是实现移动语义的智能指针(在文档中省略)。 QVariant 有点类似于std::any 并且有模板方法
template<typename T> inline T value() const;
宏Q_DECLARE_METATYPE 允许将Test 类型的值放在QVariant 中。
问题:
行m = v.value<Test>().m; 似乎调用了value() 返回的临时对象的字段m 的移动赋值。之后,Test 析构函数被调用,并在试图访问非法地址时立即崩溃。
一般来说,我看到的问题是,虽然移动分配使对象本身处于一致状态,但包含移动实体的对象的状态“意外”发生了变化。
有几种方法可以避免这个特定示例中的问题,我能想到:将 Test 析构函数更改为预期 null m,编写模板 template<typename T> inline T no_move(T&& tmp) {return tmp;},在 Test 中显式创建临时对象 assign,为m 添加getter 并调用它来强制复制m(由Jarod42 建议); MS Visual Studio 允许写std::swap(m, v.value<Test>().m),但是这个代码是非法的。
问题:
是否有一种“正确的”(最佳实践?)方式来显式调用复制分配(或以某种方式正确调用swap)而不是移动?有没有办法禁止析构函数中使用的类字段的移动语义?为什么首先将移动临时对象成员设为默认选项?
【问题讨论】:
-
将移动构造函数声明为已删除,即
Test(Test&&) = delete;。但是修复你的班级以允许在没有 UB 的情况下移动会更好。 -
(a) 我不认为
Test的 move 构造函数是这里的问题(实施它以强制复制没有任何改变,并且描述的解决方案(看似)工作而不会弄乱它)。 (b) 删除它会破坏Q_DECLARE_METATYPE(因为QVariant按值返回对象)。 -
你也可以使用 getter 来避免移动。
-
猜测
value<Test>()会返回一个副本,但Test类没有复制构造函数。那么那里会发生什么? -
检查析构函数中的共享指针。移动后它可以变成一个nullptr。如果确实如此,请不要取消引用它。 OTOH,如果您想要一个准确的计数器,您确实需要定义自己的复制 ctor 和复制分配,并在那里增加它。
标签: c++ c++11 move-semantics