简体   繁体   English

什么时候可以安全地从C ++中的基类继承

[英]When can I safely inherit from a base class in C++

So the basic rule that I find everywhere is that to inherit from a base class, the base class must have a virtual destructor so that the following works: 因此,我到处都发现的基本规则是要从基类继承,该基类必须具有虚拟析构函数,以便进行以下工作:

Base *base = new Inherited();
delete base;

However I am certain I have seen at least one other possibility that allows safe inheritance. 但是我可以肯定,我至少看到了另一种允许安全继承的可能性。 However I can't find it anywhere and I feel like I am going mad trying to find it. 但是我在任何地方都找不到它,我觉得自己要去寻找它会发疯。 I thought the other option might have been that the base class had a trivial destructor, but according to Non-virtual trivial destructor + Inheritance , this isn't the case. 我以为其他选择可能是基类有一个琐碎的析构函数,但是根据非虚拟琐碎的析构函数+继承 ,情况并非如此。 Even though there wouldn't be a memory leak for this case, it appears this is still undefined behaviour. 即使在这种情况下也不会发生内存泄漏,但看来这仍然是不确定的行为。

Does anyone else know what the other case is or can you definitively tell me that I dreamt it? 还有其他人知道另一种情况吗?或者您可以明确地告诉我我梦到了吗?

I guess an example can be the one that involves shared_ptr s, for it is good to show both the sides of the issue. 我想一个例子可能是涉及shared_ptr的例子,因为最好同时展示问题的两面。

Suppose you have a class B with a trivial non virtual destructor and a derived class D with its own complex one. 假设您有一个带有琐碎的非虚拟析构函数的类B和一个带有其自身的复数的派生类D

Let's define the following function somewhere: 让我们在某处定义以下函数:

shared_ptr<B> factory () {
    // some complex rules at the very end of which you decide to instantiate class D
    return make_shared<D>();
}

In that case you are dealing with all the interesting features due to the polymorphism, but the pointer you are working with has inherited the deleter from the one constructed with type D . 在这种情况下,由于多态性,您需要处理所有有趣的功能,但是您使用的指针继承了D类型构造的deleter

Even though, thanks to the type erasure, the type is buried somewhere and everything works fine, the actual invoked destructor is the one of D , so everything should work fine also from that point of view, even though the destructor of B was not virtual . 即使由于类型擦除,该类型被埋在某个地方并且一切正常,实际调用的析构函数是D的一个,因此从那个角度来看,即使B的析构函数不是virtual ,一切也应该正常工作。

Instead, if you define the above factory as: 相反,如果您将上述工厂定义为:

B* factory () {
    return new D{};
}

The called destructor (well, supposing that someone will delete it) will be the one of B , that is not what you want. 被调用的析构函数(假设有人将其删除)将是B ,这不是您想要的。

That said, defining as virtual the destructor of a class that is meant to be inherited from is a good practice, otherwise put a final in the class definition and stop there the hierarchy. 就是说,将要从其继承的类的析构函数定义为virtual的是一种好习惯,否则在类定义中添加一个final并在此处停止层次结构。

There also a lot of other examples, this is not the only case where it works, but it can help to explain why it works. 还有很多其他的例子,这是不是在那里工作的唯一案例,但它可以帮助解释为什么它的工作原理。

Perhaps when the inheritance is private. 也许继承是私有的。 In such a case, the user can't convert Derived* to Base* so there is no chance of trying to delete the derived class through the base class pointer. 在这种情况下,用户无法将Derived*转换为Base*因此没有机会尝试通过基类指针删除派生类。 Of course, you still have to watch that you don't do this anywhere within your implementation of Derived . 当然,您仍然必须注意在Derived的实现中没有在任何地方执行此操作。

My take on this is pragmatic rather than anything to do with what is or isn't allowed by the standards. 我认为这是务实的,而不是与标准所允许或不允许的任何事情有关。

So, pragmatically , if a class doesn't have a virtual destructor - even an empty one - then my default assumption is that it hasn't been designed to be used as a base class . 因此, 务实地 ,如果一个类没有虚拟析构函数,甚至没有一个空的析构函数,那么我的默认假设是该类没有被设计为用作基类 This may have more implications than just destruction and in more cases than not, just opens a can of worms for you to fall in later. 这可能不仅具有破坏性,而且具有更多的含义,在更多情况下,只是打开一罐蠕虫供您稍后使用。

If you want or need to use functionality from a class without a virtual destructor, it would be safer to use composition rather than inheritance. 如果您希望或需要使用没有虚拟析构函数的类中的功能,那么使用合成而不是继承会更安全。 In fact, that's the preferred route anyway. 实际上,无论如何,这是首选路线。

The other case I've seen mentioned is making the base-class destructor protected . 我见过的另一种情况是使基类析构函数 protected That way, you prevent deletion through a base class. 这样,您可以防止通过基类进行删除。

This is actually item 50 in the book C++ Coding Standards by Herb Sutter et al: "Make base class destructors public and virtual or protected and non-virtual", so it is quite likely that you have heard of it before. 实际上,这是Herb Sutter等人在C ++编码标准书中的第50条:“使基类析构函数成为公共的和虚拟的或受保护的和非虚拟的”,因此您很可能以前已经听说过。

You can always inherit from a class. 您始终可以从类继承。 There are rules to obey though, eg without a virtual destructor you can't invoke the destructor polymorphically. 但是有一些规则要遵守,例如,没有虚拟析构函数,您将无法多态调用析构函数。 In order to avoid this, you could eg use private derivation for baseclasses that were not intended as baseclasses, like eg the containers from the STL. 为了避免这种情况,您可以例如对不打算作为基类的基类使用私有派生,例如STL中的容器。

As others have mentioned, as long as you delete the class through it's own destructor - in other words you do 正如其他人所提到的,只要您通过自己的析构函数删除该类-换句话说,

Inherited *ip = new Inherited();
Base *p = ip; 
... 
delete ip;

you'll be fine. 你会没事的。 There are several different ways to do that, but you have to be quite careful to ensure that is the case. 有几种不同的方法可以做到这一点,但是您必须非常小心以确保确实如此。

However, having an empty destructor in the baseclass [and your inherited type is immediately inheriting] only works as long as it is TRULY empty, and not just that you have an { } for the body of the destructor. 但是,在基类中有一个空的析构函数[并且您的继承类型将立即继承]仅在TRULY为空的情况下才有效,而不仅是析构函数的主体具有{ } [See Edit2 below!] [请参阅下面的Edit2!]

For example, if you have a vector or std::string , or whatever other class that needs destruction, in your baseclass, then you will leak the content of that class. 例如,如果在基类中有vectorstd::string或任何其他需要销毁的类,则将泄漏该类的内容。 In other words, you need to make 100% sure that the destructor of the baseclass is empty. 换句话说,您需要100%确保基类的析构函数为空。 I don't know of a programmatic way to determine that (beyond analysing the generated code, that is). 我不知道一种确定程序的方式(除了分析生成的代码外)。

Edit: 编辑:

Also beware of "changes in the future" - for example, adding a string or vector inside Base or changing the base class from Base to SomethingInheritedFromBase that has a destructor "with content" will ruin the "empty destructor" concept. 也要提防“未来的变化”-例如,在Base内添加字符串或向量,或将基类从Base更改为具有析构函数“ with content”的SomethingInheritedFromBase ,将破坏“空析构函数”的概念。

Edit2: 编辑2:

It should be noted that for the "destructor is empty", you have to have true empty destuctors in all derived clases too. 应该注意的是,对于“析构函数为空”,在所有派生类中也必须具有真正的空析构函数。 There are classes that have no members that need destruction (interface classes typically have no data members, for example, so would not need destruction in themselves), so you could construct such a case, but again, we have to be VERY careful to avoid the destructor of a derived class adding a destructor into the class. 有些类没有需要销毁的成员(例如,接口类通常没有数据成员,因此它们本身不需要销毁),因此可以构造这种情况,但是同样,我们必须非常小心以避免派生类的析构函数,将析构函数添加到该类中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM