【发布时间】:2015-04-13 20:24:09
【问题描述】:
我问过a question about move constructors,我还没有接受答案,因为我对这个问题的某些方面感到更加困惑,即使我开始掌握其他方面。特别是,我发现了一个令人惊讶的案例,其中 g++ 和 clang++ 都生成了不正确的移动构造函数。
问题总结
- g++ 和 clang++ 显然违反了明确定义析构函数时不生成移动构造函数的规则;为什么?这是一个错误,还是我误解了发生了什么?
- 为了正确起见,这些(可能是非法的)移动构造函数应该使 RHS 指针成员无效,但它们不会。为什么不呢?
- 看来,避免不良行为的唯一方法是为在其析构函数中使用
delete的每个类显式定义一个正确 移动构造函数。 Qt 库(5.4 版)会这样做吗?
第 1 部分:非法自动生成的构造函数?
考虑以下代码:
class NoMove
{
public:
~NoMove() {}
};
int main()
{
std::cout << "NoMove move-constructible? " <<
std::is_move_constructible<NoMove>::value << std::endl;
}
使用g++ 4.9.2 和clang++ 3.5.1 编译,此代码打印:
NoMove move-constructible? 1
...但是由于NoMove 有一个明确定义的析构函数,我希望neither a move constructor nor a copy constructor should be auto-generated。请注意,意外的构造函数生成并不是因为析构函数是微不足道的;当析构函数 delete[]s 是一个数组 (!!) 时,我会得到相同的行为,我什至能够编译 需要 有效移动构造函数 (!!!!!) 的代码。 (见下面的例子。)这里发生了什么?在这里自动生成移动构造函数是否合法,如果是,为什么?
第 2 部分:(可能是非法的)自动生成的构造函数导致未定义的行为?
似乎在涉及delete 时提供安全移动构造函数is fairly simple,但我只是想确保我理解:当一个类包含指针成员并且拥有基础数据时,是否存在任何种情况,在将目标指针设置为旧值后,移动构造函数使 RHS 指针无效?
考虑以下示例,它与上面的NoMove 示例类似,并且基于我的original question:
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 有一个自动生成的移动构造函数。当然,在运行时,它会导致双重删除错误。
现在,这没关系,如果自动生成的移动构造函数使 RHS 指针无效。所以,如果可以在这里自动生成移动构造函数,为什么不这样做安全?使这项工作的移动构造函数很简单:
DataType(DataType&& rhs) :
val{rhs.val}
{
rhs.val = nullptr;
}
(对吗?我错过了什么吗?应该是val{std::move(rhs.val)}?)
这似乎是一个非常安全的自动生成功能;编译器知道 rhs 是一个 r 值,因为函数原型是这样说的,因此修改它是完全可以接受的。因此,即使DataType 的析构函数没有 delete[] val,似乎也没有任何理由不 使rhs 在自动-生成的版本,但我认为这会导致微不足道的性能损失。
因此,如果编译器正在自动生成这个方法——同样,它不应该,特别是因为我们可以使用unique_ptr 从标准库代码中轻松获得这种精确行为-- 为什么它会自动生成它不正确?
第 3 部分:在 Qt 中避免这种行为(尤其是 Qt 5.4 中的 QByteArray)
最后,一个(希望如此)简单的问题:Qt 5.4 的堆分配类,如 QByteArray(这是我在原始问题中实际使用的 DataType)正确实现了移动构造函数,使任何移出的拥有指针无效?
我什至懒得问,因为 Qt 看起来很可靠,而且我还没有看到任何双重删除错误还,但考虑到这些不正确的编译器让我措手不及——生成的移动构造函数,我担心在其他实现良好的库中很容易得到不正确的移动构造函数。
与此相关,在C++11 之前编写的没有显式移动构造函数的 Qt 库呢?如果我不小心强制了一个在这种情况下行为错误的自动生成的移动构造函数,有没有人知道使用符合 C++11 的编译器编译 Qt 3 是否会在这样的用例中导致未定义的破坏行为?
【问题讨论】:
-
我投票结束这个问题,因为老兄。每个问题一个问题。
-
@Barry 这让它……跑题了?你真的读过全文吗?这些问题非常相关,联系在一起,甚至是累积的。简化后,它们可能是:“1)为什么标准编译器在这种情况下会生成一个 move-ctor?2)为什么生成的 ctor 不正确?3)Qt 是否避免了这个错误?”
-
我确实阅读了整本书。此问题包含三个单独的问题。
-
我认为,如果你把它分成更小的问题,你会得到更好的结果,因为你不太可能一口气得到一本必要的小说来回答所有这些问题。
-
@barry 我不同意,但我知道你会如何得出这个结论。我已经重写了问题的某些部分,以尝试以更统一的方式呈现它。
标签: c++ qt pointers c++11 move-semantics