在 C++11 中,您实际上会想要执行
virtual ~A() = default;
。否则,您将失去隐式移动构造函数。
virtual ~A() = default;
是什么?为什么使用 virtual ~A() {}
会丢失隐式移动构造函数?在 C++11 中,您实际上会想要执行
virtual ~A() = default;
。否则,您将失去隐式移动构造函数。
virtual ~A() = default;
是什么?为什么使用 virtual ~A() {}
会丢失隐式移动构造函数?这条评论不正确。
两者:
virtual ~A() = default;
和
virtual ~A() {}
如果析构函数是用户声明的,则禁止隐式移动成员。 用户声明的特殊成员在[dcl.fct.def.default] / p4中讨论了用户声明和用户提供。
如果一个特殊成员函数是用户声明的,且在其第一次声明时未明确默认或删除,则它就是用户提供的。
~A() = default;
可能是一个微不足道的析构函数,但 ~A() {}
不行。 - bames53在这篇文章中https://dev59.com/AnPYa4cB1Zd3GeqPj4Zk#17204598,我有一条评论:
在C++11中,您实际上要执行
virtual ~A() = default;
否则,您将失去隐式移动构造函数。
该评论是不正确的。
即使使用了default
,那个析构函数也是“用户声明的”(尽管请注意它不是“用户提供的”)。
#include <iostream>
struct Helper
{
Helper() {}
Helper(const Helper& src) { std::cout << "copy\n"; }
Helper(Helper&& src) { std::cout << "move\n"; }
};
struct A
{
virtual ~A() {}
Helper h;
};
struct B
{
virtual ~B() = default;
Helper h;
};
struct C
{
Helper h;
};
int main()
{
{
A x;
A y(std::move(x)); // outputs "copy", because no move possible
}
{
B x;
B y(std::move(x)); // outputs "copy", because still no move possible
}
{
C x;
C y(std::move(x)); // outputs "move", because no user-declared dtor
}
}
+ g++-4.8 -std=c++11 -O2 -Wall -pthread main.cpp
+ ./a.out
copy
copy
move
所以你并没有“丢失”任何东西 —— 最开始就没有移动功能!
这是标准中禁止在两种情况下隐式声明移动构造函数的规定:
[C++11: 12.8/9]:
如果类X
的定义没有显式声明移动构造函数,当且仅当:
X
没有用户声明的复制构造函数,X
没有用户声明的复制赋值运算符,X
没有用户声明的移动赋值运算符,X
没有用户声明的析构函数,并且- 移动构造函数不会被隐式定义为已删除。
如果未来的标准能够明确列出“用户声明”的精确含义,那就更好了。至少有这么一段:
[C++11: 8.4.2/4]:
[..] 如果一个特殊成员函数是用户声明的,并且在其第一次声明时没有显式指定为默认或删除,则它是用户提供的。 [..]
可以暗示出这里的区别。
= default;
似乎是一样的。 - legends2k12.8/9
可以改为“用户提供”,12.8/20
也是如此吧? - Lightness Races in Orbitstruct A { ~A() = default; }; A::~A() {}
但是定义是非法的。我认为这可能只是笨拙或过于精确的措辞。 - Lightness Races in Orbit这个评论是错误的。
如果你想让编译器提供一个移动构造函数,而不是提供自己的移动构造函数,那么其中一个要求就是它期望由编译器提供析构函数,也就是一个平凡的析构函数。然而,当前标准对于何时可以提供隐式实现非常严格——接受用户如何提供析构函数。任何由用户声明的内容都被认为是用户自己掌握的事情,因此不仅如此。
~A() { … }
~A() = default;
这个语法使得编译器不提供隐式析构函数。第一个是定义,因此也是声明;第二个只是声明。在这两种情况下,析构函数都是用户声明的,从而禁止编译器提供隐式移动构造函数。
我猜测背后的原因是,在移动期间,对象的资源被移动到另一个对象中,留下原始对象处于没有动态存储资源的状态;但如果你的类没有这样的资源,那么它可以轻松地移动、销毁等。当你声明一个非平凡的析构函数时,这提示编译器你管理的类的资源并不是一些简单的东西,你大多数情况下需要提供非平凡的移动,所以编译器不提供一个。
A(A&&) = default;
。 - Xeo