我什么时候可以安全地从C++中的基类继承
When can I safely inherit from a base class in C++
所以我在任何地方都能找到的基本规则是,要从基类继承,基类必须有一个虚拟析构函数,这样才能实现以下功能:
Base *base = new Inherited();
delete base;
然而,我确信我至少看到了另一种允许安全继承的可能性。然而,我在任何地方都找不到它,我觉得我要疯了。我认为另一种选择可能是基类有一个琐碎的析构函数,但根据非虚拟琐碎析构函数+继承,事实并非如此。尽管在这种情况下不会发生内存泄漏,但这似乎仍然是未定义的行为。
其他人知道另一种情况是什么吗?或者你能肯定地告诉我是我梦见的吗?
我想一个例子可以是涉及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>();
}
在这种情况下,由于多态性,您正在处理所有有趣的特性,但您正在处理的指针从用类型D
构造的指针继承了deleter
。
尽管由于类型擦除,类型被隐藏在某个地方,一切都很好,但实际调用的析构函数是D
的析构构函数,因此从这个角度来看,一切都应该很好,即使B
的析构因子不是virtual
。
相反,如果您将上述工厂定义为:
B* factory () {
return new D{};
}
所谓的析构函数(好吧,假设有人会删除它)将是B
中的一个,这不是你想要的。
也就是说,将要继承的类的析构函数定义为virtual
是一种很好的做法,否则将final
放在类定义中,并停止层次结构。
还有很多其他的例子,这不是中工作的唯一情况,但它可以帮助解释为什么它工作。
可能是在继承是私有的情况下。在这种情况下,用户无法将Derived*
转换为Base*
,因此无法尝试通过基类指针删除派生类。当然,您仍然需要注意,在Derived
的实现中,您不会在任何地方这样做。
我对此的看法是务实的,而不是与标准允许或不允许的内容有关。
因此,务实地,如果一个类没有虚拟析构函数,即使是空的,那么我的默认假设是它没有被设计成基类。这可能比破坏有更多的影响,在更多的情况下,这只是打开了一个蠕虫罐头,让你以后陷入其中。
如果您希望或需要使用没有虚拟析构函数的类中的功能,那么使用组合而不是继承会更安全。事实上,这是首选路线。
我提到的另一种情况是使基类析构函数protected
。这样,就可以防止通过基类进行删除。
这实际上是Herb Sutter等人的《C++编码标准》一书中的第50项:"使基类析构函数公共且虚拟,或受保护且非虚拟",所以你很可能以前听说过它。
您总是可以从类继承。不过,也有一些规则需要遵守,例如,如果没有虚拟析构函数,就不能以多态方式调用析构函数。为了避免这种情况,例如,您可以对不打算作为基类的基类使用私有派生,例如STL中的容器。
正如其他人所提到的,只要你通过它自己的析构函数删除类,换句话说,你就完成了
Inherited *ip = new Inherited();
Base *p = ip;
...
delete ip;
你会没事的。有几种不同的方法可以做到这一点,但你必须非常小心,以确保情况确实如此。
但是,基类中有一个空的析构函数[并且您继承的类型是立即继承的],只有当它为TRULY空时才有效,而不仅仅是析构函数体有一个{ }
。[参见下面的第2版!]
例如,如果基类中有一个vector
或std::string
,或者任何其他需要销毁的类,那么就会泄露该类的内容。换句话说,您需要100%确保基类的析构函数为空。我不知道有什么编程方法可以确定这一点(除了分析生成的代码之外)。
编辑:
还要注意"未来的变化"——例如,在Base
中添加字符串或向量,或者将基类从Base
更改为具有"带内容"析构函数的SomethingInheritedFromBase
,都会破坏"空析构函数"概念。
第2版:
需要注意的是,对于"析构函数为空",在所有派生类中也必须有真正的空析构函数。有些类没有需要销毁的成员(例如,接口类通常没有数据成员,因此本身不需要销毁),因此可以构造这样的情况,但我们必须非常小心,避免派生类的析构函数向类中添加析构函数。
- std::具有相同基类的类的变体
- 是否可以初始化不可复制类型的成员变量(或基类)
- 在C++中,是否可以基于给定的标识符创建基类的新实例,反之亦然
- 基类中的函数名称解析
- C++初始化基类
- 如何通过派生类函数更改基类中的向量
- 如何定义一个纯抽象基类
- 如何使用基类指针引用派生类成员
- 继承:构造函数,初始化C++11中基类的类C数组成员
- 使用基类指针创建对象时,缺少派生类析构函数
- 如何引用基类的派生类?
- 如果基类包含双指针成员,则派生类的构造函数
- 在模板基类中为继承类中的可选重写生成虚拟方法
- 为什么此派生对象无法访问基类的后递减方法?
- 公开最直接的基类模板名称
- 当基类是依赖类型时,这是一个缺陷吗
- 如何使基类的运算符对基类的可变参数数可见(请参阅下面的代码)?
- 模板基类中的静态变量
- C++ 继承:将子类传递给需要基类的函数并获取子类行为
- 继承和友元函数,从基类访问受保护的成员