【问题标题】:Is it safe to privately inherit from a class with a non-virtual destructor?从具有非虚拟析构函数的类中私有继承是否安全?
【发布时间】:2012-09-09 23:40:36
【问题描述】:

我知道使用公共继承通常安全,因为当delete使用基类指针时,编译器只生成代码来调用基类的析构函数,而派生类的析构函数是没有调用。

但是对于私有继承,客户端不能将派生类指针转换为基类指针(因为私有继承不模拟 is-a 关系),所以delete 总是用于派生类的指针,编译器应该能够看到还有一个基类并调用它的析构函数。

我做了这个测试:

#include <iostream>

struct BaseVirtual
{
    virtual ~BaseVirtual()
    {
        std::cout << "BaseVirtual's dtor" << '\n';
    }
};

struct BaseNonVirtual
{
    ~BaseNonVirtual()
    {
        std::cout << "BaseNonVirtual's dtor" << '\n';
    }
};

struct DerivedPrivVirtual: private BaseVirtual
{
    static void f()
    {
        BaseVirtual * p = new DerivedPrivVirtual;
        delete p;
    }

    ~DerivedPrivVirtual()
    {
        std::cout << "DerivedPrivVirtual's dtor" << '\n';
    }
};

struct DerivedPrivNonVirtual: private BaseNonVirtual
{
    static void f()
    {
        BaseNonVirtual * p = new DerivedPrivNonVirtual;
        delete p;
    }

    ~DerivedPrivNonVirtual()
    {
        std::cout << "DerivedPrivNonVirtual's dtor" << '\n';
    }
};

int main()
{
    std::cout << "With explicit derived pointer type:" << '\n';
    {
        DerivedPrivVirtual * derivedPrivVirtual = new DerivedPrivVirtual;
        DerivedPrivNonVirtual * derivedPrivNonVirtual = new DerivedPrivNonVirtual;

        delete derivedPrivVirtual;
        delete derivedPrivNonVirtual;
    }
    std::cout << '\n';

    std::cout << "With base pointer type:" << '\n';
    {
        // Client code can't cast Derived to Base when inherit privately.
        //BaseVirtual * derivedPrivVirtual = new DerivedPrivVirtual;
        //BaseNonVirtual * derivedPrivNonVirtual = new DerivedPrivNonVirtual;

        //delete derivedPrivVirtual;
        //delete derivedPrivNonVirtual;
    }
    std::cout << '\n';

    std::cout << "Inside derived class itself:" << '\n';
    {
        DerivedPrivVirtual::f();
        DerivedPrivNonVirtual::f();
    }
    std::cout << '\n';

    std::cout << "With non-dynamic variables:" << '\n';
    {
        DerivedPrivVirtual derivedPrivVirtual;
        DerivedPrivNonVirtual derivedPrivNonVirtual;
    }
    std::cout << '\n';
}

GCC 4.7.1 和 CLang 3.1 提供相同的输出。除非派生类本身将派生类指针强制转换为基类并且deletes 它,否则将调用派生类构造函数。

除了这种看起来很不常见且很容易避免的情况(类的作者是唯一可以造成伤害的人,但它确实知道它是从哪个类派生出来的),我可以断定它是安全的吗?

With explicit derived pointer type:
DerivedPrivVirtual's dtor
BaseVirtual's dtor
DerivedPrivNonVirtual's dtor
BaseNonVirtual's dtor

With base pointer type:

Inside derived class itself:
DerivedPrivVirtual's dtor
BaseVirtual's dtor
BaseNonVirtual's dtor  <-- Only a problem inside the class itself

With non-dynamic variables:
DerivedPrivNonVirtual's dtor
BaseNonVirtual's dtor
DerivedPrivVirtual's dtor
BaseVirtual's dtor

额外问题:受保护的继承呢?我想伤害的能力不再是直接派生类的作者的特权,而是层次结构中任何类的作者的特权。

【问题讨论】:

  • 如果我没记错的话,Scott Meyers(Effective C++,More Effective C++ 的作者)仍然不知道受保护的继承是什么意思。 Public 是“is-a”关系,Private 是“implemented-in-terms-of”,但受保护?这有点吓人。

标签: c++ inheritance destructor


【解决方案1】:

无论继承是公有的还是私有的,都不会影响代码的安全性,它只是限制了它可以安全/不安全地使用的范围。您有相同的基本问题:如果您的类或您的类的朋友将您的类型的对象传递给一个接口,该接口采用指向没有虚拟析构函数的基的指针,并且如果该接口获得您的对象的所有权,那么您正在创建 undefined行为。

设计中的问题是,根据您的问题,BaseNonVirtual 并非旨在扩展。如果是,它应该有一个公共的虚拟析构函数,或者一个受保护的非虚拟析构函数,确保没有代码能够通过指向基的指针对派生对象调用 delete。

【讨论】:

  • 任何结构都可能被误用。声明私有数据成员是否安全?哦不,你可能会不小心把它设置在不合适的地方! “安全”通常意味着“不会被他人滥用”,私有继承与私有数据成员一样安全。
  • @nm:如果你仔细阅读第一段,你应该注意到它提到了这种构造将失败的情况:如果你的类 [..] 将你类型的对象传递给...。它还在第二段中提到了这个漏洞的解决方案。只要您不产生指向成员的指针,私有继承就与私有数据成员一样安全,但这并不常见。另一方面,继承通常用于提供指向基的指针——如果继承(ab)用于获得不同设计问题的功能。
【解决方案2】:

有一种情况,尽管私有继承,客户端代码可以将 Derived 转换为 Base:

delete reinterpret_cast&lt;BaseNonVirtual*&gt;(new DerivedPrivNonVirtual);

因此跳过~DerivedPrivNonVirtual()的执行。

但是,鉴于不鼓励使用 reinterpret_cast,您可能会得出结论认为它对于您的目的“足够安全”。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-12
    • 2011-11-16
    • 2016-08-14
    • 1970-01-01
    • 2016-03-04
    • 2020-03-10
    • 2012-02-04
    相关资源
    最近更新 更多