所以我在任何地方找到的基本规则是从基类继承,基类必须有一个虚拟析构函数,以便以下工作:
Base *base = new Inherited();
delete base;
但我确信我至少看到过另一种允许安全继承的可能性。但是,我无法在任何地方找到它,我觉得我很想找到它。我认为另一种选择可能是基类有一个简单的析构函数,但根据Non-virtual trivial destructor + Inheritance,情况并非如此。即使在这种情况下不会出现内存泄漏,但看起来这仍然是未定义的行为。
有没有其他人知道其他案件是什么,或者你能否明确地告诉我,我曾梦想过它?
答案 0 :(得分:3)
我想一个例子可以是涉及shared_ptr
的例子,因为最好显示问题的两个方面。
假设您有一个类B
,其中包含一个简单的非虚拟析构函数和一个具有自己的复杂类的派生类D
。
让我们在某处定义以下功能:
shared_ptr<B> factory () {
// some complex rules at the very end of which you decide to instantiate class D
return make_shared<D>();
}
在这种情况下,由于多态性,您正在处理所有有趣的功能,但您使用的指针继承了deleter
类型构造的D
。
即使由于类型擦除,类型被隐藏在某处并且一切正常,实际调用的析构函数是D
之一,所以从这个角度来看,一切都应该正常工作,即使B
的析构函数不是virtual
。
相反,如果您将上述工厂定义为:
B* factory () {
return new D{};
}
被叫析构函数(好吧,假设有人会将其删除)将是B
中的一个,这不是你想要的。
也就是说,将virtual
定义为要继承的类的析构函数是一种很好的做法,否则将final
置于类定义中并停止层次结构。
还有很多其他的例子,这不是其中的唯一案例,但它可以帮助解释为什么它的工作原理。
答案 1 :(得分:2)
也许当继承是私有的时候。在这种情况下,用户无法将Derived*
转换为Base*
,因此无法尝试通过基类指针删除派生类。当然,您仍需要注意在Derived
的实现中,您不会在任何地方执行此操作。
答案 2 :(得分:2)
我对此的看法是务实的,而不是与标准允许或不允许的任何事情有关。
所以,实用,如果一个类没有虚拟析构函数 - 即使是一个空的析构函数 - 那么我的默认假设是它还没有被设计为用作基类。这可能有更多的含义,而不仅仅是破坏,在更多的情况下,只是打开一堆蠕虫让你陷入困境。
如果您希望或需要使用没有虚拟析构函数的类中的功能,那么使用组合而不是继承会更安全。事实上,无论如何,这是首选路线。
答案 3 :(得分:1)
我见过的另一个案例是制作基类析构函数 protected
。这样,您就可以防止通过基类进行删除。
这实际上是Herb Sutter等人的C ++编码标准中的第50项:“使基类析构函数公共和虚拟或受保护和非虚拟”,所以很可能你以前听过它。 / p>
答案 4 :(得分:0)
您始终可以从班级继承。但是有一些规则要遵守,例如如果没有虚拟析构函数,则无法以多态方式调用析构函数。为了避免这种情况,您可以例如对非基本类的基类使用私有派生,例如来自STL的容器。
答案 5 :(得分:0)
正如其他人所提到的,只要您通过它自己的析构函数删除该类 - 换句话说就是
Inherited *ip = new Inherited();
Base *p = ip;
...
delete ip;
你会没事的。有几种不同的方法可以做到这一点,但你必须非常小心,以确保是这种情况。
但是,在基类中有一个空的析构函数[并且你的继承类型会立即继承]只有在TRULY为空时才有效,而不仅仅是析构函数体的{ }
。 [见下面的Edit2!]
例如,如果您的基类中有vector
或std::string
或其他需要销毁的类,那么您将泄漏该类的内容。换句话说,您需要100%确定基类的析构函数为空。我不知道一种确定方法的程序化方法(除了分析生成的代码之外)。
修改强>
还要注意未来的变化&#34; - 例如,在Base
内添加字符串或向量,或者将基类从Base
更改为具有析构函数的SomethingInheritedFromBase
&#34;内容为&#34;将毁掉空洞的破坏者&#34;概念
<强> EDIT2:强>
应该注意的是,对于&#34;析构函数为空&#34;,你必须在所有派生的clases中都有真正的空目标。有些类没有需要销毁的成员(例如,接口类通常没有数据成员,因此本身不需要破坏),所以你可以构造这样的情况,但同样,我们必须非常小心避免派生类的析构函数,将析构函数添加到类中。