【问题标题】:Private Derived Destructor私有派生析构函数
【发布时间】:2017-10-08 23:52:33
【问题描述】:

当我试图以多态方式删除派生对象时(即:基类具有 public virtual destructor),为什么仍然调用派生类 private destructor?为什么范围解析 private 在这里不起作用。

class Base
{
protected:
    Base() { cout << "Base constructor.\n"; }
public:
    virtual ~Base() { cout << "Base destructor.\n"; }
};

class Derived :public Base
{
public:
    Derived() { cout << "Derived constructor.\n"; }
private:
   ~Derived() { cout << "Derived destructor.\n"; }
};

int main()
{
    Base *p = new Derived();
    delete p;
}

输出:

Base constructor.
Derived constructor.
Derived destructor.
Base destructor.

【问题讨论】:

  • 因为你是通过基类指针调用派生dtor...

标签: c++ oop virtual-destructor scope-resolution


【解决方案1】:

因为析构函数的调用顺序与构造函数相反,所以虚析构函数总是会被调用。

private 与要调用的虚函数无关。

正如我在这里指出的:

Why would a virtual function be private?

ISO C++ 1998 及以后的标准明确指出:

§10.3 [...] 在确定是否覆盖时不考虑访问控制(第 11 条)。


有点哲学的题外话:

更进一步,这就是 STL 对 iostreams 所做的事情:Non-Virtual Interface 的定义,即所有公共函数(析构函数除外)都是非虚拟的,所有虚拟函数都是 protectedprivate。公共函数称为虚拟保护或私有函数。这为整个层次结构提供了一个非常清晰的入口点。

【讨论】:

    【解决方案2】:

    是,但您没有直接调用~Derived()。如果你要使用

    Derived *p = new Derived();
    delete p;
    

    然后你会得到错误。但是当你通过多态间接访问~Derived()(例如通过调用~Base())时,访问说明符private不适用。

    根据[class.access.virt#1]

    虚函数的访问规则(子句 [class.access])由其声明决定,不受稍后覆盖它的函数规则的影响。 [ 示例:

    class B {
    public:
      virtual int f();
    };
    
    class D : public B {
    private:
      int f();
    };
    
    void f() {
      D d;
      B* pb = &d;
      D* pd = &d;
    
      pb->f();                      // OK: B​::​f() is public, D​::​f() is invoked
      pd->f();                      // error: D​::​f() is private
    }
    

    — 结束示例 ]

    又在footnote 111 in [class.virtual]:

    在确定覆盖时不考虑访问控制。

    【讨论】:

      【解决方案3】:

      您使用虚拟析构函数是因为您希望调用继承中的所有析构函数。这正是你得到的。私有不适用,因为您没有显式调用析构函数。

      如果你的析构函数不是虚拟的,你只会得到Base::~Base() 的调用。当你有多态性时,通常这不是你想要的。

      【讨论】:

        【解决方案4】:

        您可以通过指向B 的指针调用析构函数,因为它已经在那里公开。在这种情况下,它是用于确定访问的指针的静态类型。

        [class.access.virt/1]

        虚函数的访问规则(子句 [class.access])是 由其声明确定,不受规则的影响 稍后覆盖它的函数。 [ 示例:

        class B {
        public:
          virtual int f();
        };
        
        class D : public B {
        private:
          int f();
        };
        
        void f() {
          D d;
          B* pb = &d;
          D* pd = &d;
        
          pb->f();                      // OK: B​::​f() is public, D​::​f() is invoked
          pd->f();                      // error: D​::​f() is private
        }
        

        — 结束示例 ]

        这使虚函数与Liskov Substitution Principle保持一致

        【讨论】:

          【解决方案5】:

          您通过基类指针删除派生类。在虚拟析构函数的帮助下,您可以在派生类中开始删除。因为该类可以访问其私有成员,所以它调用私有析构函数。之后,基类调用公共析构函数。

          请注意,您调用 delete 而不是直接调用析构函数 Derived::~Derived()!

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2014-05-17
            • 2021-03-20
            • 2015-04-16
            • 2011-11-16
            • 1970-01-01
            • 2017-11-14
            • 2013-03-30
            • 1970-01-01
            相关资源
            最近更新 更多