3

When I am trying to delete the derived object polymorphically (that is: base class has public virtual destructor) why derived class private destructor is still being called? Why the scope resolution private is not working here.

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;
}

Output:

Base constructor.
Derived constructor.
Derived destructor.
Base destructor.
jotik
  • 17,044
  • 13
  • 58
  • 123
Sahib Yar
  • 1,030
  • 11
  • 29

5 Answers5

2

It is, but you're not calling ~Derived() directly. If you were to use

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

then you'd get the error. But when you access ~Derived() indirectly via polymorphism (e.g. by calling ~Base()), then the access specifier private does not apply.

According to [class.access.virt#1]:

The access rules (Clause [class.access]) for a virtual function are determined by its declaration and are not affected by the rules for a function that later overrides it. [ Example:

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
}

— end example ]

And again in footnote 111 in [class.virtual]:

Access control is not considered in determining overriding.

jotik
  • 17,044
  • 13
  • 58
  • 123
  • This rule makes sense: The implementer of a polymorphic base class cannot know what later generations will derive from it; code relying on the base class' contract must continue to function even with derived classes added later. What confused me a bit is that in the case of destructors, which are named like their classes, the overriding is not obvious. In `struct A { virtual ~A(){} }; struct B: public A { ~B() override {} }; void f() { A *ap = new B; ap->~A(); }` the call `ap->~A();` actually calls, perhaps surprisingly, `~B()`, despite the name. – Peter - Reinstate Monica Dec 07 '22 at 14:05
2

Because destructors are called in reversed order of constructors and virtual destructor will always be called.

private has nothing to do if a virtual function is going to be called.

As I pointed here out:

Why would a virtual function be private?

ISO C++ 1998 Standard onwards explicitly states:

§10.3 [...] Access control (clause 11) is not considered in determining overriding.


A bit philosophical offtopic:

Going further this is what STL does for iostreams: Definition of Non-Virtual Interface, i.e. all public functions (with exception of destructors) are non-virtual and all virtual functions are either protected or private. Public functions call virtual protected or private ones. This gives a very clear entry point into the entire hierarchy.

Community
  • 1
  • 1
ovanes
  • 5,483
  • 2
  • 34
  • 60
  • The answer has almost nothing to do with the question. You discuss the design decision to separate interface from customization (and implement the latter with private but overridable virtual functions). One difference to note is that in this pattern the base has private functions. And importantly, e.g. in Herb Sutter's article, **the code in the private derived class functions never calls private functions from the base class.** It couldn't. That's what private means (as opposed to protected). – Peter - Reinstate Monica Dec 07 '22 at 11:42
  • In this question, by contrast, **the base member function is public**, namely the destructor. The result is a bit involved: Even though the two destructors have different names the derived one still overrides the base one. The result is that you call the private derived destructor through pointer to base: *User code effectively calls a private member function.* That is very different from Sutter's virtual private customization functions which are never called from user code (only the non-virtual interface functions are called by users, the rest is inaccessible by design). – Peter - Reinstate Monica Dec 07 '22 at 13:58
0

You use a virtual destructor because you want all the destructor in your inheritance to get called. That exactly what you get. The private does not apply as you don't call the destructor explicitly.

If your destructor was not virtual you will get only Base::~Base() called. Usually that's not what you want when you have polymorphism.

neuro
  • 14,948
  • 3
  • 36
  • 59
  • In fact, if your destructor was not virtual the behavior would be undefined. And yes, you don't call the destructor explicitly, but nor do you in the opposite case, where the destructor is private in the base class. And yet *that* is forbidden: `class A { virtual ~A() {} }; struct B: A { };` does not compile, even though nothing was called anywhere, let alone explicitly. – Peter - Reinstate Monica Dec 07 '22 at 14:10
0

You can call the destructor through a pointer to B, because it's already public there. It's the static type of the pointer which is used to determine that access in this case.

[class.access.virt/1]

The access rules (Clause [class.access]) for a virtual function are determined by its declaration and are not affected by the rules for a function that later overrides it. [ Example:

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
}

— end example ]

This keeps virtual functions in line with the Liskov Substitution Principle

Community
  • 1
  • 1
StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
0

You delete the derived class through the base class pointer. With the help of the virtual destructor you start the delete at the derived class. Because that class can access its private members, it call the private destructor. After that the base class is calling the public destructor.

Note that you call delete and not calling de destructor Derived::~Derived() direct!

T.Buys
  • 26
  • 3