自动生成的移动构造函数导致非法行为
Autogenerated move constructors causing illegal behavior
我问了 我还没有接受答案,因为我对问题的某些方面感到更加困惑,即使我开始掌握其他方面。特别是,我发现了一个令人惊讶的案例,其中 g++ 和 clang++ 都生成了不正确的移动构造函数。
问题总结
- g++和clang++显然违反了显式定义析构函数时不生成移动构造函数的规则;为什么?这是一个错误,还是我误解了什么?
- 为了正确起见,这些(可能非法的)移动构造函数应该使 RHS 指针成员无效,但它们没有。为什么不呢?
- 似乎避免不良行为的唯一方法是为每个 class 中使用
delete
的 class 显式定义 correct 移动构造函数析构函数。 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[]
是一个数组 (!!) 时,我得到了相同的行为,我什至能够编译 需要 有效移动构造函数的代码 (!!!! !)。 (参见下面的示例。)这是怎么回事?在这里自动生成移动构造函数是否合法,如果是,为什么?
第 2 部分:(可能是非法的)自动生成的构造函数导致未定义的行为?
似乎在涉及 delete
时提供安全移动构造函数 is fairly simple,但我只是想确保我理解:当 class 包含指针成员并且 拥有底层数据,是否存在任何情况,在这种情况下,移动构造函数在设置目标后使RHS指针无效是不正确和充分的指向旧值的指针?
考虑以下示例,它类似于上面的 NoMove
示例,并且基于我的 :
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
是一个右值,因为函数原型是这样说的,因此修改它是完全可以接受的。所以即使DataType
的析构函数没有delete[] val
,似乎也没有任何理由没有 在自动生成的版本中使 rhs
无效,除了,我想,因为这会导致微不足道的性能损失。
因此,如果编译器自动生成此方法——同样,它不应该,尤其是因为我们可以轻松地从标准库中获得此确切行为代码使用 unique_ptr
-- 为什么自动生成它 不正确 ?
第 3 部分:在 Qt 中避免这种行为(尤其是在 Qt 5.4 中 QByteArray
)
最后,一个(希望如此)简单的问题:做 Qt 5.4 的堆分配 classes,例如 QByteArray
(这是我实际使用的 DataType
在我原来的问题中)已经正确实现了移动构造函数,使任何移出的拥有指针无效?
我什至懒得问,因为 Qt 看起来很可靠而且我还没有看到任何双重删除错误还,但考虑到我措手不及由于这些不正确的编译器生成的移动构造函数,我担心在其他方面实现良好的库中很容易以不正确的移动构造函数结束。
相关地,在 C++11
之前编写的没有显式移动构造函数的 Qt 库呢?如果我不小心强制了一个在这种情况下行为错误的自动生成的移动构造函数,有谁知道使用 C++11 兼容的编译器编译 Qt 3 是否会在这样的用例中导致未定义的破坏行为?
问题是您混淆了 is_move_constructible
和 "has a move constructor"。 is_move_constructible<T>
不测试 T 是否有移动构造函数。它测试 T
是否可以从 T
类型的右值构造。 const T&
可以绑定到 T
右值。
您看到的是自动生成的 copy 构造函数 T(const T&)
正在执行它的工作 - 并且惨遭失败。
I would expect that neither a move constructor nor a copy constructor should be auto-generated.
您的 link 谈到移动构造函数。不说拷贝构造函数,不声明总是隐式声明的
现在,如果您声明了移动操作,隐式声明的复制构造函数将被定义为已删除,但您没有这样做,因此它被定义为默认并执行成员复制。 [class.copy]/p7:
If the class definition does not explicitly declare a copy
constructor, one is declared implicitly. If the class definition
declares a move constructor or move assignment operator, the
implicitly declared copy constructor is defined as deleted; otherwise,
it is defined as defaulted (8.4). The latter case is deprecated if the
class has a user-declared copy assignment operator or a user-declared
destructor.
我问了
问题总结
- g++和clang++显然违反了显式定义析构函数时不生成移动构造函数的规则;为什么?这是一个错误,还是我误解了什么?
- 为了正确起见,这些(可能非法的)移动构造函数应该使 RHS 指针成员无效,但它们没有。为什么不呢?
- 似乎避免不良行为的唯一方法是为每个 class 中使用
delete
的 class 显式定义 correct 移动构造函数析构函数。 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[]
是一个数组 (!!) 时,我得到了相同的行为,我什至能够编译 需要 有效移动构造函数的代码 (!!!! !)。 (参见下面的示例。)这是怎么回事?在这里自动生成移动构造函数是否合法,如果是,为什么?
第 2 部分:(可能是非法的)自动生成的构造函数导致未定义的行为?
似乎在涉及 delete
时提供安全移动构造函数 is fairly simple,但我只是想确保我理解:当 class 包含指针成员并且 拥有底层数据,是否存在任何情况,在这种情况下,移动构造函数在设置目标后使RHS指针无效是不正确和充分的指向旧值的指针?
考虑以下示例,它类似于上面的 NoMove
示例,并且基于我的
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
是一个右值,因为函数原型是这样说的,因此修改它是完全可以接受的。所以即使DataType
的析构函数没有delete[] val
,似乎也没有任何理由没有 在自动生成的版本中使 rhs
无效,除了,我想,因为这会导致微不足道的性能损失。
因此,如果编译器自动生成此方法——同样,它不应该,尤其是因为我们可以轻松地从标准库中获得此确切行为代码使用 unique_ptr
-- 为什么自动生成它 不正确 ?
第 3 部分:在 Qt 中避免这种行为(尤其是在 Qt 5.4 中 QByteArray
)
最后,一个(希望如此)简单的问题:做 Qt 5.4 的堆分配 classes,例如 QByteArray
(这是我实际使用的 DataType
在我原来的问题中)已经正确实现了移动构造函数,使任何移出的拥有指针无效?
我什至懒得问,因为 Qt 看起来很可靠而且我还没有看到任何双重删除错误还,但考虑到我措手不及由于这些不正确的编译器生成的移动构造函数,我担心在其他方面实现良好的库中很容易以不正确的移动构造函数结束。
相关地,在 C++11
之前编写的没有显式移动构造函数的 Qt 库呢?如果我不小心强制了一个在这种情况下行为错误的自动生成的移动构造函数,有谁知道使用 C++11 兼容的编译器编译 Qt 3 是否会在这样的用例中导致未定义的破坏行为?
问题是您混淆了 is_move_constructible
和 "has a move constructor"。 is_move_constructible<T>
不测试 T 是否有移动构造函数。它测试 T
是否可以从 T
类型的右值构造。 const T&
可以绑定到 T
右值。
您看到的是自动生成的 copy 构造函数 T(const T&)
正在执行它的工作 - 并且惨遭失败。
I would expect that neither a move constructor nor a copy constructor should be auto-generated.
您的 link 谈到移动构造函数。不说拷贝构造函数,不声明总是隐式声明的
现在,如果您声明了移动操作,隐式声明的复制构造函数将被定义为已删除,但您没有这样做,因此它被定义为默认并执行成员复制。 [class.copy]/p7:
If the class definition does not explicitly declare a copy constructor, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy constructor is defined as deleted; otherwise, it is defined as defaulted (8.4). The latter case is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor.