C++11 接口纯虚析构函数

9
UPD。有一个标记显示这是这个问题的副本。但在那个问题中,OP询问如何使用default来定义纯虚析构函数。而这个问题是关于有什么区别

在C++(尽可能使用最新的标准)中,使用空体实现定义纯虚析构函数和只使用空体(或默认值)之间的真正区别是什么?

变体1:

class I1 {
public:
    virtual ~I1() {}
};

Variant 2.1:
变体2.1:
class I21 {
public:
    virtual ~I21() = 0;
};

I21::~I21() {}

变体2.2:

class I22 {
public:
    virtual ~I22() = 0;
};

I22::~I22() = default;

更新:我发现Variant 1和Variant 2.1/2.2之间至少有1个区别:

std::is_abstract::value对于Variant 1是false,而对于Variant 2.1和2.2是true

演示

也许有人能找到2.1和2.2之间的差异吗?


关于虚拟继承的问题是什么? - curiousguy
3个回答

15

正如你所指出的,I1和I2*之间的区别在于添加= 0使得该类成为抽象类。实际上,将析构函数设置为纯虚函数是一种“技巧”,当您没有其他函数需要设置为纯虚函数时,可以使用它来使类成为抽象类。我说它是一个技巧,是因为如果你想销毁它的任何派生类(并且你确实会这样做),则不能将析构函数保留未定义状态,而是仍然需要定义析构函数,无论是空的还是默认的。

现在,空析构函数/构造函数和默认析构函数/构造函数(I21和I22)之间的区别要复杂得多,几乎没有详细的文献说明。推荐使用default,这不仅是一种新的编程风格,可以让您的意图更加清晰,而且显然可以给编译器提供优化机会。引用自msdn

由于平凡特殊成员函数的性能优势,我们建议在希望使用默认行为时,优先使用自动生成的特殊成员函数而不是空函数体。

除了这种可能的性能提升外,两者之间没有任何明显的区别。从C++11开始,= default是首选。


4
我所找到的内容如下:
§12.4 (5.9)
析构函数可以声明为虚拟的(10.3)或纯虚拟的(10.4);如果程序中创建了该类或任何派生类的对象,则必须定义析构函数。如果一个类有一个带有虚拟析构函数的基类,无论是用户定义的还是隐式声明的析构函数都是虚拟的。
导致:
§10.4 (现在该类是抽象的)
10.4 (2)说:
只有在使用合格ID语法(5.1)调用纯虚拟函数时才需要定义它。
但是,在§12.4中关于析构函数的叙述表明,析构函数总是按其完全限定名称调用(以防止歧义)。
这意味着:
- 必须定义析构函数,即使是纯虚拟的。 - 该类现在是抽象的。

小注:这些段落中并没有要求定义析构函数,相反的,如果你想要一个“静态类”(即每个成员/成员函数都是静态的类),你不需要定义析构函数。 - lorro
@lorro 提供的是正确的,只要你不创建该类的对象。但有人会想知道,在这种情况下为什么要声明析构函数... - Richard Hodges

2

变体1允许您拥有该类的实例。变体2.1、2.2不允许实例,但允许后代的实例。例如,以下代码(能够让许多人感到困惑)是可行的,但如果去掉标记的行,将导致编译失败:

class I21 {
public:
    virtual ~I21() = 0;
};

I21::~I21() {} // remove this and it'll not compile

class I22 : public I21
{
public:
    virtual ~I22() {}
};

int main() {
    I22 i;
    return 0;
}

原因是析构函数链直接调用I21::~I21(),而不是通过接口。也就是说,你对纯虚析构函数的目标并不清楚。如果你想避免实例化(即静态类),你可以考虑删除构造函数;如果你想让后代可以实例化但不包括这个类,也许你需要一个在后代中实现的纯虚成员函数。


当然,在所有变量中(包括变量1),我意味着该类将包含其他纯虚成员... - vladon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接