15

我知道使用公共继承通常是不安全的,因为当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 都给出相同的输出。派生类构造函数被调用,除非派生类本身将派生类指针强制转换为基类并传递delete它。

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

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

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

4

2 回答 2

10

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

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

于 2012-09-17T18:01:12.133 回答
1

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

delete reinterpret_cast<BaseNonVirtual*>(new DerivedPrivNonVirtual);

从而跳过~DerivedPrivNonVirtual().

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

于 2014-12-02T03:46:45.803 回答