繁体   English   中英

将受保护的析构函数设为虚拟是否有用?

[英]Is there a use for making a protected destructor virtual?

/*Child is inherited from Parent*/
class Parent {  
  public:  
    Parent () //Constructor
    {
        cout << "\n Parent constructor called\n" << endl;
    }
  protected:
    ~Parent() //Dtor
    {
        cout << "\n Parent destructor called\n" << endl;
    }
};

class Child : public Parent 
{
  public:
    Child () //Ctor
    {
        cout << "\nChild constructor called\n" << endl;
    }
    ~Child() //dtor
    {
        cout << "\nChild destructor called\n" << endl;
    }
};

int main ()
{
    Parent * p2 = new Child;          
    delete p2;
    return 0;
}

如果我将Parent的析构函数设为虚拟,则会出现错误,那么将受保护的析构函数设为虚拟的目的是什么?

举一个例子:假设你有一个实现引用计数的基类。 你有一个addRef和一个release方法,并且你希望你的对象被销毁,如果(且当)内部计数器通过调用release达到零。

所以,首先你希望你的析构函数受到保护(因为你只想从release中销毁对象)。

如果您打算从您的类派生,您还希望您的析构函数是虚拟的,因为每当您想通过指向基类的指针销毁子对象时都需要一个虚拟析构函数(感谢@sharptooth 的提示......)

C++ 核心指南中有一个专门针对这个特定主题的条目

C.35: 基类析构函数应该是公共的和虚拟的,或者是受保护的和非虚拟的

原因防止未定义的行为。 如果析构函数是公共的,则调用代码可以尝试通过基类指针销毁派生类对象,如果基类的析构函数是非虚拟的,则结果未定义。 如果析构函数是受保护的,那么调用代码就不能通过基类指针进行销毁,并且析构函数不需要是虚拟的; 它确实需要受到保护,而不是私有,以便派生的析构函数可以调用它。 通常,基类的编写者不知道销毁时要执行的适当操作。

因此,如果它受到保护,析构函数就不需要是虚拟的。 但是,有一个例外:

例外我们可以想象一种情况,您可能需要一个受保护的虚拟析构函数:当一个派生类型的对象(并且只有这种类型)应该被允许通过指向基的指针来销毁另一个对象(而不是它自己)时。 不过,我们在实践中还没有看到这样的案例。

因此,总而言之,实际上受保护的析构函数不需要是虚拟的。

是的,如果您打算在class Parent成员函数中delete this ,这在 COM 对象中实现IUnknown::Release()时很常见。

protected: Base::~Base(); 如果您(计划)删除从BaseBase的派生类中Base的任何对象,则至少应该是虚拟的。

MartinStettner 的回答基本上描述了 COM。 为了使事情更清楚,请考虑以下代码(引用自 Microsoft 的Implementing IUnknown in C++ ):

#include <Windows.h>

class Base
{
public:
    Base() = default;

    ULONG AddRef() { return InterlockedIncrement(&m_cRef); }

    ULONG Release()
    {
        const ULONG cRef = InterlockedDecrement(&m_cRef);
        if (0 == cRef)
        {
            delete this;
        }
        return cRef;
    }
protected:
    virtual ~Base() { printf(__FUNCTION__ "\n"); }
    ULONG m_cRef{1};
};

class Derived : public Base
{
public:
    Derived() = default;
protected:
    ~Derived() { printf(__FUNCTION__ "\n"); }
};

int main()
{
    Base* bar = new Derived;
    //delete bar; // C2248: 'Base::~Base': cannot access protected member declared in class 'Base'
    bar->Release();

    Derived* foo = new Derived;
    //delete foo; // C2248: 'Derived::~Derived': cannot access protected member declared in class 'Derived'
    foo->Release();

    return 0;
}

输出:

Derived::~Derived
Base::~Base
Derived::~Derived
Base::~Base

如果virtual~Base()中删除,输出将丢失Derived::~Derived的两个实例,这是预期的,因为Basedelete this this的 this 类型是Base*类型。

因此,如果您不希望调用者执行delete bar / delete foo并希望调用基类和派生类的析构函数,则必须保护两个析构函数并且至少基类的析构函数必须是虚拟的。

暂无
暂无

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

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