虚析构函数必须是公有的吗?

18

我发现几乎每个虚析构函数的代码片段都将其定义为公有成员函数,就像这样:

class Base
{
public:
    virtual ~Base()
    {
        cout << "~Base()" << endl;
    }
};
class Derived : public Base
{
public:
    ~Derived()
    {
        cout << "~Derived()" << endl;
    }
};
虚析构函数必须是公开的吗?还是有一些情况下非公开虚析构函数是有意义的?

除非使用delete this从派生类型中排除,否则通常是有道理的。 - Zdeslav Vojkovic
1
@SteveJessop,你说得对。我没有明确表明我是在继承链的上下文中谈论,每个级别都可以安全地调用delete this,如果基类是虚拟的。 - Zdeslav Vojkovic
@StoryTeller - 如果你没有访问权限,就不能删除指向基类的指针。将析构函数设置为受保护或私有并没有本质上的错误,但它会限制谁可以删除指针。 - Pete Becker
@PeteBecker。我在哪里说禁止访问析构函数是错误的? - StoryTeller - Unslander Monica
@StoryTeller - 不能在没有访问权限的情况下调用delete。这并不一定意味着“在类外”。友元可以这样做,而且不必成为成员。 - Pete Becker
显示剩余3条评论
5个回答

23
虚构函数必须是公共的吗?还是有一些情况下非公共的虚构函数更合理呢?
因地制宜。如果需要多态删除,则使用公共的虚构函数;否则,您的析构函数根本不需要是虚构函数。
遵循Herb's advice
指南#4:基类析构函数应为公共且虚拟或受保护且非虚拟。
简而言之,您将面临以下两种情况之一。 要么:
1. 您希望通过基类指针允许多态删除,在这种情况下,析构函数必须是虚拟和公共的;要么
2. 您不需要多态删除,在这种情况下,析构函数应为非虚拟和受保护的,后者可防止不需要的使用。

3
但是这个建议假定您永远不想通过基指针允许多态删除,仅限于派生类。鉴于该问题特别涉及虚析构函数始终为公共的动机,我认为Sutter在这里的解释存在差距。例如,解释可能是“派生类可以将基指针强制转换为正确的派生类型,然后删除它”,但这本身也有一些假设。 - Steve Jessop
据我所知,如果您需要进行多态删除,则必须使析构函数为虚函数;否则,没有理由使析构函数成为虚函数。还有哪些情况需要处理?或者我可能误解了您要表达的观点。 - Alok Save
2
我正在想象一个类层次结构,在其中delete总是在叶子类中调用,但是叶子类想要删除的东西不一定是相同的叶子类的实例,它可能是另一个类。因此,基类需要一个虚析构函数,它可以是受保护的而不是公共的,因为只有基类的派生类使用它。这是一个奇怪的设计,从Sutter的角度来看可能不值得一提,因为任何想出这样的设计的人都可以自己想出后果。但我认为它不能被拒绝为不可能。 - Steve Jessop
3
如果你想通过一个基类指针进行多态删除,但只想在基类本身中进行,该怎么办?那么析构函数必须是虚函数,但不必是公共函数。例如,基类可能为整个继承层次结构提供引用计数(或其他内存管理)。 - Steve Jessop
@SteveJessop:我现在理解你的观点了。是的,Sutter的建议没有考虑到一些不寻常的设计情况,其中一些你描述得非常恰当。它更像是一个样板式的设计模式建议。我相信它假设大多数坚持这个样板的人都是为了多态删除,而其他通过相当奇怪的设计到达这种情况的人将能够自行权衡利弊。在你新的解释性评论的光芒下,我同意你最初的评论,谈论Sutter的辩解中的空白 - Alok Save
显示剩余2条评论

6
就像非虚拟析构函数一样,它们不需要是公共的,但大多数情况下它们是公共的。如果你的类是一个例外情况,并且由于任何原因需要控制其实例的生命周期,则析构函数必须是非公共的。这会影响客户端如何使用类的实例,但这当然是整个重点。并且由于析构函数是虚拟的,唯一的其他选择就是虚拟受保护的析构函数。
相关链接:是否有必要使受保护的析构函数为虚拟的?

@ChristianSeverin:我只是使用了问题中相同的单词。但是进行了一些小修改,事情变得更好了,感谢您的提示。 - Jon
而且(我认为)你可以排除private virtual析构函数,因为具有私有析构函数的基类根本无法被销毁。对于不打算作为基类的类来说,私有析构函数可能是有意义的,但如果它不是基类,那么它就没有虚拟的必要了。 - Steve Jessop
@SteveJessop:我也是这么想的(“另一种选择就是virtual protected”)。 - Jon
@Jon:是的,我正在尝试为那个陈述提供理由,也就是明确排除它而不是含糊其辞地不提及它 :-) - Steve Jessop

2
如果您计划通过特殊方法(例如create/destroy)创建/销毁对象,则不需要。但如果您在堆栈或堆上创建对象,则必须有公共析构函数。

1
这里的问题是关于虚析构函数的,因此我认为需要考虑实现此类功能所需的原因排列组合,包括继承情况。对于此问题的答案取决于以下内容:
1)如果您不希望类被实例化,则可以使用私有构造函数/析构函数。但是,实例化可以由同一类中的另一个方法完成。因此,当您想要在类内部使用特定方法(如MyDestructor())来调用析构函数时,析构函数仍然可以放置在private中。 例如:单例设计模式。此外,在这种情况下,它还防止了类被继承。
2)如果确实打算继承该类,则不允许使用私有基类析构函数(会引发编译错误)。但是,受保护的基类析构函数允许继承。
3)受保护的虚析构函数的继承类型(公共和受保护)允许安全地进行多级继承A->B->C,以便在调用C的析构函数时更好地清理内存。
4)仅使用私有析构函数无法允许删除动态分配的内存(我不确定auto_ptr是否也应遵循使用“private”析构函数的相同思路)。

我看到很多人使用私有析构函数可能会出现错误,特别是当不知道这种实现方式的人即将使用这个类时。

保护和公共析构函数总是受欢迎的,使用取决于上述需求。

希望这可以澄清问题。


0

这里涉及到两条独立的规则。首先,如果您的设计通过一个指向基类的指针删除派生类型的对象,则基类中的析构函数必须是虚拟的。其次,如果一个成员函数(包括析构函数在内)是受保护或私有的,则可以调用它的上下文比公共的更受限制(当然,如果析构函数是私有的,就不能从该类派生)。例如:

class C {
protected:
    virtual ~C();
    friend void destroy_me(C*);
};

void destroy_me(C *cp) {
    delete cp; // OK: destructor is accessible
}

void destroy_someone_else(C *cp) {
    delete cp; // Error: destructor is not accessible
}

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