C++ 自动生成的移动构造函数导致非法行为
我问了一个我还没有接受答案的问题,因为我对这个问题的某些方面感到更加困惑,即使我开始掌握其他方面。特别是,我发现了一个令人惊讶的例子,其中g++和clangg++都生成了不正确的移动构造函数 问题摘要C++ 自动生成的移动构造函数导致非法行为,c++,qt,pointers,c++11,move-semantics,C++,Qt,Pointers,C++11,Move Semantics,我问了一个我还没有接受答案的问题,因为我对这个问题的某些方面感到更加困惑,即使我开始掌握其他方面。特别是,我发现了一个令人惊讶的例子,其中g++和clangg++都生成了不正确的移动构造函数 问题摘要 g++和clang++显然违反了在显式定义析构函数时不生成移动构造函数的规则;为什么?这是一个错误,还是我误解了发生了什么 为了正确起见,这些(可能是非法的)移动构造函数应该使RHS指针成员无效,但它们不会。为什么不呢 似乎避免不必要行为的唯一方法是为在其析构函数中使用delete的每个类显式定
- g++和clang++显然违反了在显式定义析构函数时不生成移动构造函数的规则;为什么?这是一个错误,还是我误解了发生了什么
- 为了正确起见,这些(可能是非法的)移动构造函数应该使RHS指针成员无效,但它们不会。为什么不呢
- 似乎避免不必要行为的唯一方法是为在其析构函数中使用
的每个类显式定义一个正确的移动构造函数。Qt库(版本5.4)是否执行此操作delete
class NoMove
{
public:
~NoMove() {}
};
int main()
{
std::cout << "NoMove move-constructible? " <<
std::is_move_constructible<NoMove>::value << std::endl;
}
…但是由于NoMove有一个显式定义的析构函数,我希望是这样。请注意,意外的构造函数生成不是由于析构函数是平凡的这一事实;当析构函数delete[]
s生成数组(!!)时,我会得到同样的行为,我甚至能够编译需要有效移动构造函数的代码(!!!!!)。(见下面的例子)这里发生了什么?在这里自动生成移动构造函数合法吗?如果合法,为什么
第2部分:(可能非法)导致未定义行为的自动生成构造函数?
似乎需要在delete
时提供安全的移动构造函数,但我只想确保我理解:当类包含指针成员并拥有底层数据时,在任何情况下,移动构造函数在将目标指针设置为旧值后使RHS指针无效都是不正确和不充分的吗
考虑以下示例,它类似于上面的NoMove
示例,并且基于我的:
这可以很好地编译——表明,DataType
有一个自动生成的移动构造函数。当然,当运行时,它会导致双重删除错误
现在,如果自动生成的move构造函数使RHS指针无效,这就没问题了。那么,如果可以在这里自动生成移动构造函数,为什么这样做不安全呢?实现此功能的移动构造函数很简单:
DataType(DataType&& rhs) :
val{rhs.val}
{
rhs.val = nullptr;
}
(对吗?我遗漏了什么吗?应该是val{std::move(rhs.val)}
?)
这似乎是一个完全安全的自动生成功能;编译器知道rhs
是一个r值,因为函数原型这么说,因此修改它是完全可以接受的。因此,即使DataType
的析构函数没有delete[]val
,似乎也没有任何理由不在自动生成的版本中使rhs
无效,除非,我认为,这会导致微不足道的性能损失
因此,如果编译器自动生成这个方法——同样,它不应该自动生成,特别是因为我们可以使用unique\u ptr
从标准库代码中轻松地获得这个精确行为——为什么它会错误地自动生成它呢
第3部分:在Qt中避免这种行为(特别是Qt5.4中的QByteArray
)
最后,一个(希望如此)简单的问题:Qt5.4的堆分配类,如QByteArray
(这就是我在原始问题中实际用作数据类型的类)是否正确地实现了移动构造函数,从而使从拥有指针移动的所有指针无效
我甚至懒得问,因为Qt看起来很可靠,我还没有看到任何双删除错误,但考虑到这些错误的编译器生成的移动构造函数让我措手不及,我担心在一个实现良好的库中很容易出现错误的移动构造函数
与此相关的是,在C++11
之前编写的没有显式移动构造函数的Qt库呢?如果我可以意外地强制一个自动生成的move构造函数在这种情况下表现出错误,那么有人知道,比如说,使用符合C++11的编译器编译Qt3是否会在这样的用例中导致未定义的销毁行为吗?问题在于,您混淆了是可构造的
还是“有一个move构造函数”<代码>是否可移动\u可构造
不测试t是否有移动构造函数。它测试是否可以从类型为T
的右值构造T
。而const T&
可以绑定到T
rvalue
您看到的是自动生成的复制构造函数T(const T&)
正在执行其工作,并且失败得很惨
我希望移动构造函数和复制构造函数都不应该自动生成
你的链接讨论了移动构造函数。它没有提到复制构造函数,如果不声明它,它总是隐式声明的
现在,如果您声明了一个移动操作,隐式声明的复制构造函数将被定义为deleted,但您没有这样做,因此它被定义为default并执行memberwise复制。[class.copy]/p7:
如果类定义没有显式声明副本
构造函数,则隐式声明一个。如果类定义
声明移动构造函数或移动赋值运算符
隐式声明的复制构造函数定义为已删除;否则,,
它被定义为默认值(8.4)。如果
类具有用户声明的复制赋值运算符或用户声明的
析构函数
我投票结束这个问题,因为老兄。每个问题一个问题。@Barry,这就……离题了?你真的读了整件事吗?这些问题非常复杂
class DataType
{
public:
DataType()
{
val = new int[35];
}
~DataType()
{
delete[] val;
}
private:
int* val;
};
class Marshaller
{
public:
Marshaller()=default;
DataType toDataType() &&
{
return std::move(data);
}
private:
DataType data;
};
void DoMarshalling()
{
Marshaller marshaller;
// ... do some marshalling...
DataType marshalled_data{std::move(marshaller).toDataType()};
}
DataType(DataType&& rhs) :
val{rhs.val}
{
rhs.val = nullptr;
}