首页 > 解决方案 > 当我可以确定我可以从 c++ 类继承?

问题描述

假设我使用了一个包含各种类的外部库。我什么时候可以安全地从这些类之一继承?我知道基类必须有一个虚拟析构函数。在使用该类作为基类之前我还应该检查什么?我可以确定只有文档说明它是安全的吗?

标签: c++

解决方案


如果文档声明派生类型是安全的,请遵循文档。如果由于某种原因,它的行为方式与文档背道而驰,那么这是库的问题,并且是作者修复或提供解决方法的错误,因为他们没有承诺他们保证的 API在文档中。


任何不是的类型final都可以“安全地”派生;更重要的是如何处理和销毁这种类型。如果您从没有析构函数的类型继承virtual,这本质上不会破坏任何东西;如果您将其从句柄破坏到基类,它只会阻止调用派生类型的析构函数。

如果您只将类型从句柄销毁到派生类型(例如,您要么具体持有它,要么从不从句柄到基类销毁它),那么这没有任何后果。

为了更好地解释我的观点,想象以下层次结构:

class Base {
public:
    // No virtual destructor
    ...
};

class Derived : public Base {
public:
    ...
private:
    std::string m_something; // some leakable object
};

Derivedfrom的推导Base是完全安全的。重要的是如何销毁它是否会出现问题。为此,需要考虑两种不同的情况:自动和动态情况。

自动对象

自动类型(“按值”类型)是安全的,无论它们是否具有静态生命周期

auto d = Derived{ ... };
static auto sd = Derived{ ... };

在它们的生命周期结束时,Derived::~Derived将调用析构函数,因为类型是具体已知的

动态对象

动态对象不会自行销毁。他们的资源最终需要被清理,要么通过智能指针中的 RAII 自动清理,要么由某人调用delete,或者由某人显式调用~T()并释放内存。

如果它们被派生类型的句柄销毁,它们仍然是安全的,但如果它们被基类的句柄销毁,它们就不是安全的了。

auto* d1 = new Derived{ ... }; 
auto* d2 = new Derived{ ... };

// Deleting as a pointer to Base; ~Derived won't be called because ~Base is virtual
// This would be a memory leak
delete static_cast<Base*>(d1);  // bad 

// Deleting as a pointer to Derived -- ~Derived will be called, this is fine
delete d2; // good

在智能指针类型方面:

共享指针

shared_ptr类型是安全的,因为它们总是从具体类型中销毁对象——即使它们被别名为基类。

void accept_base(std::shared_ptr<Base> b);

auto d = std::make_shared<Derived>(...);

// still safe
accept_base(std::move(d));
唯一指针

unique_ptr默认情况下,类型是不安全的,T因为默认删除器会根据unique_ptr.

例如:

auto d = std::make_unique<Derived>(...);

auto b = std::unique_ptr<Base>{std::move(d)};

// b will be destroyed at end of scope by calling ~Base, which is not virtual!

即使说了这么多:如果您使用的库明确声明您打算派生一些 XYZ 类,那么您仍然应该假设这就是该类的使用方式。那时,如果发生不希望的事情,则由库维护者来确保他们的代码按文档说明执行,因为它是他们明确声明的 API 的一部分。


推荐阅读