C++抽象类析构函数

8

在父类中仅使用纯虚析构函数来创建一个抽象类,这是否是良好的实践(并且是否可能)?

以下是一个示例:

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

class Derived : public AbstractBase {
public:
    Derived() {}
    virtual ~Derived() {}
};

否则,如果派生类的属性和构造函数都相同,而其他方法完全不同,我该如何创建一个抽象类?

3
那被称为空接口,是可以实现的。用于什么目的? - user703016
1
不是一个“空”接口,正如我所说的。也被称为“标记”接口。 - user703016
1
我很难想到一个使用这个的案例。你确定不应该创建一个多态函数吗? - Per Johansson
4
你可能忘了加上 virtual 关键字。但我的观点是我不理解你会用这个类/接口做什么。它似乎毫无用处。虽然我不是 C++ 的专家... - Per Johansson
3
这个问题更适合放在http://programmers.stackexchange.com/上进行讨论。 - R Sahu
显示剩余9条评论
3个回答

23

C++中的接口应该有一个已实现但不执行任何操作的虚析构函数。接口中的所有其他方法都必须定义为抽象的(例如,在声明中添加=0)。这将确保您无法创建接口类的实例,但是一旦将接口指针分配给父对象,您可以删除对象。以下是一些代码,抱歉措辞不当:

class ISampleInterface
{
public:
    virtual ~ISampleInterface() { };
    virtual void Method1() = 0;
    virtual void Method2() = 0;
}

class SampleClass : public ISampleInterface
{
public:
    SampleClass() { };
    void Method1() { };
    void Method2() { };
}

int main()
{
    ISampleInterface *pObject = new SampleClass();
    delete pObject;
    return 0;
}
    
    

9
在基类中仅有一个纯虚析构函数通常不是一个好的实践,但是这是可能的,在某些情况下甚至是可取的。
例如,如果您依赖RTTI通过在基类指针上尝试dynamic_cast将消息分派到子类对象,则除了析构函数之外,基类中可能不需要其他方法。在这种情况下,请将析构函数设置为public virtual。
由于您除了析构函数之外没有其他虚拟方法,因此必须使其成为纯虚函数,以防止创建基类对象。
在这里,关键是为该析构函数提供空的主体(即使它是“= 0”!)
struct AbstractBase {
     virtual ~AbstractBase() = 0;
}

AbstractBase::~AbstractBase() {
}

拥有 body 可以创建子类对象(前提是子类定义了析构函数且未将其设置为纯虚函数)。

你能写一个简单的纯虚析构函数的例子吗?我有些困惑... - user3758182
你确定子类必须定义一个析构函数吗?我可以在MSVC中编译一个带有=0{...一些代码...}和空派生类的示例。正如你所说,不能为基类创建任何对象(纯虚拟),但我可以创建一个派生类的对象:当销毁时,它会调用基类析构函数中的{}中的代码。 - Christophe
@Christophe 换句话说,可以将基础对象仅作为派生类对象的子对象创建。这不是自然而然想要的吗?如果您想禁止创建子类对象,请将子类的析构函数也定义为纯虚函数。在子类中没有必要定义析构函数,但如果子类必须清理某些资源,则必须定义它。 - Oleg

4
这个问题让我想起了一个“老新奇事”(Old New Thing)的帖子:(链接)
其中说到:“一位客户询问如何完成某个任务……我并不确定这是个好主意,就像询问如何用牙齿接住棒球或者怎样把芝士汉堡上的所有芝士都去掉。”
我解释了他们方法的几个缺陷……最后总结道:“这个想法充满了危险,我担心我的回答会被视为赞同而不是勉强提供帮助。”
可以创建一个只有virtual析构函数的抽象基类。但我不确定你为什么要这样做。知道一个对象派生自某个类型(比如Foo)应该意味着我知道我可以对这个对象做什么。但在这种情况下,我只知道我可以销毁这个对象,这并没有什么用处。
但你说得对,最显然的方法无法编译:
struct Foo {
    virtual ~Foo() = 0;
};

struct Bar : Foo {
    ~Bar() { }
};

struct Baz : Foo {
    ~Baz() override { } // C++11
};

当我尝试编译这段代码时,出现了链接错误。因为虽然Bar::~Bar()Baz::~Baz()已经定义了,但是Foo::~Foo()没有定义,而编译器在销毁Bar或者Baz时会调用Foo::~Foo()
有两种解决方法。第一种是,虚函数不需要是纯虚函数:
struct Foo {
    virtual ~Foo() { }
};

struct Bar : Foo {
    ~Bar() { }
};

struct Baz : Foo {
    ~Baz() override { } // C++11
};

struct Quux : Foo { };

但是你特别要求一个纯虚析构函数。答案确实很奇怪:纯虚函数可以由声明它们为纯虚函数的类定义:

struct Foo {
    virtual ~Foo() = 0;
};

Foo::~Foo() { }

struct Bar : Foo {
    ~Bar() { }
};

struct Baz : Foo {
    ~Baz() override { } // C++11
};

struct Quux : Foo { };

你可以这样做,并不意味着这是一个好主意。在定义函数时将一个函数声明为纯虚函数非常不寻常,因此你可能很少使用到它。
否则,如果派生类的属性和构造函数都相同,而其他方法却完全不同,那么该如何创建抽象类呢?
我认为真正困扰我的是,大多数建议现在都是使用纯虚函数来创建类似Java接口的东西。当你这样做时,你需要声明派生类需要支持的方法,但同时也让这些类可以自由地使用任何有意义的数据成员类型。而你计划只声明数据成员类型必须包含什么,并让这些类型自由地拥有截然不同的数据方法。这似乎是一个坏主意。

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