提问人:Getter 提问时间:4/13/2023 最后编辑:David GGetter 更新时间:4/13/2023 访问量:68
用户声明的析构函数不会删除隐式声明的移动构造函数 (and co) [duplicate]
User-declared destructor doesn't delete implicitly-declared move constructor (and co) [duplicate]
问:
我无法理解为什么我在类中声明析构函数时没有删除本文档中指定的隐式声明的移动构造函数,其中它说:
如果未为类类型提供用户定义的移动构造函数 (struct、class 或 union),并且以下所有条件都成立:
- 没有用户声明的复制构造函数;
- 没有用户声明的复制分配运算符;
- 没有用户声明的移动赋值运算符;
- 没有用户声明的析构函数。
然后编译器会将移动构造函数声明为非显式构造函数 具有签名的内联公共成员。
T::T(T&&)
请注意,如果我定义了自己的析构函数(因此规则为 5),则复制构造函数、复制赋值和移动赋值也应全部删除
这里有一个小代码示例来证明我的观点:
class Test
{
public:
~Test() {}
protected:
int a = 5;
};
void main()
{
Test t1;
Test t2 = std::move(t1); //shouldn't work (note : if we have a copy constructor, will work even if the move constructor doesn't exist)
}
我错过了什么?我敢肯定这是显而易见的,但我似乎找不到解释上述行为的文档。我使用 C++20 在 Visual Studio 2022 上运行代码。
在不得不在我的一个基类中创建一个虚拟析构函数并意识到我不必像我认为的那样重新定义所有复制和移动构造函数/赋值之后,我发现了上述行为。
另外,我不是 100% 清楚,为什么在理论上使用关键字专门默认任何复制/移动构造函数/赋值需要重新定义它们(+析构函数)?如果只是违约,这个选择背后的动机是什么?default
提前致谢。
答:
是的。您引用的文档是正确的。编译器没有生成移动构造函数,因为您声明了析构函数。
你所观察到的与你引用的文档并不矛盾。但是,编译器生成了更多特殊成员。从 cppreference:
如果没有为类类型(结构、类或联合)提供用户定义的复制构造函数,则编译器将始终将复制构造函数声明为其类的非显式内联公共成员。
这就是你所看到的。 调用 Copy 构造函数。Test t2 = std::move(t1);
如果删除复制构造函数,则也不会生成移动构造函数:
#include <utility>
class Test
{
public:
~Test() {}
Test() = default;
Test(const Test&) = delete;
protected:
int a = 5;
};
int main()
{
Test t1;
Test t2 = std::move(t1);
}
结果是:
<source>: In function 'int main()':
<source>:16:27: error: use of deleted function 'Test::Test(const Test&)'
16 | Test t2 = std::move(t1); //shouldn't work (note : if we have a copy constructor, will work even if the move constructor doesn't exist)
| ^
<source>:8:5: note: declared here
8 | Test(const Test&) = delete;
| ^~~~
我只能试着向你解释这背后的原因。我认为这在很大程度上是历史性的。从我有限的理解来看,我会说原始规则在让编译器生成特殊成员方面过于乐观。他们往往没有做正确的事。
考虑一下移动语义之前的情况。3 法则表示,如果实现任何特殊成员,则需要定义所有成员。编译器生成它们时的原始规则没有反映这一点。即使实现自定义析构函数,也会生成复制构造函数。这也是我们需要 3 规则的一个主要原因,因为编译器何时生成 3 的规则有点过于乐观,并且经常导致代码损坏(当程序员不遵循该规则时。编译器不跟着它)。
现在有了移动语义,事情变得更加“正确”。5 法则说,如果你实现一个,你可能还需要实现其他的。现在,这也反映在编译器生成移动构造函数的规则中。
评论
Test
Test(Test&&) = default;
评论
std::move(t1)