简体   繁体   中英

Protected non-virtual destructor in the base class

I am trying to understand virtual destructors. The following is a copy paste from this page When to use virtual destructors?

Here, you'll notice that I didn't declare Base's destructor to be virtual. Now, let's have a look at the following snippet:

 Base *b = new Derived(); // use b delete b; // Here's the problem!

[...] If you want to prevent the deletion of an instance through a base class pointer, you can make the base class destructor protected and non-virtual; by doing so, the compiler won't let you call delete on a base class pointer.

I don't understand why the deletion is prevented by having a protected non-virtual base class destructor. Doesn't the compiler think that we're trying to call delete from a base class object? What does protected have to do with that?

The C++ Standard has this to say about delete (section 5.3.5p10):

Access and ambiguity control are done for both the deallocation function and the destructor (12.4, 12.5).

Therefore, only code that has access to the destructor is able to use delete . Since the destructor is protected , that means that no one can call delete on a pointer of type Base* . Only subclasses can use the destructor at all (and the only thing that will is the subclass's own destructor, as part of the subobject destruction process).

Of course, the subclass should make its own destructor public , allowing you to delete objects through the subclass type (assuming that is the correct actual type).

NOTE: Actually, other members of Base can do delete (Base*)p; since they have access. But C++ assumes that someone using this construct will not be doing that -- C++ access control only provides guidance to code outside your class.

delete b; effectively performs b->~Base(); deallocate(b); b->~Base(); deallocate(b); . The first part - calling the destructor - would fail to compile if the destructor is inaccessible (in the same way that calling any other inaccessible method fails).

From my understanding (based on this page ), the only case we would like to use the non-virtual and protected destructor in the base class is the following:

#include <iostream>

struct unary_function {
protected:
  ~unary_function() {
      std::cout << "unary_function" << std::endl;
  }
};

struct IsOdd : public unary_function {
public:
    bool operator()(int number) { 
        return (number % 2 != 0); 
    }
};

void f(unary_function *f) {
  // compile error
  // delete f;
}

int main() {
  // unary_function *a = new IsOdd;
  // delete a;

  IsOdd *a = new IsOdd;
  delete a;

  getchar();
  return 0;
}

therefore, you can only do this:

  IsOdd *a = new IsOdd;
  delete a;

or

  IsOdd c;

never these:

  unary_function *a = new IsOdd;
  delete a;

therefore, with nonvirtual protected destructor, the compiler would give an error when you try to use this

void f(unary_function *f) {
  delete f; 
  // this function couldn't get compiled because of this delete. 
  // you would have to use the derived class as the parameter 
}

Protected methods and variables of a class (let's call it Base ) can only be accessed by derived classes. Thus, if you call delete on a pointer of type Base outside a derived class, it will try to call Base::~Base() (Base's destructor), but since it is protected, it cannot be called, which thus results in a compilation error.

According to the specification , the destructor of a base class must only be declared protected and non-virtual (to not allow deletion of a derived object through a Base pointer), or public and virtual (to allow safe deletion of a derived object through a Base pointer).

If a destructor is declared public and non-virtual, it results in undefined behaviour if a pointer of type Base which points to a derived class is deleted.

The two options:

  1. Non-virtual protected destructor - Base pointer to Derived cannot be deleted:
class Base {
public:
    Base() {
        std::cout << "Base ctor called.\n";
    }
protected:
    ~Base() {
        std::cout << "Base dtor called.\n";
    }
};

class Derived : public Base {
public:
    Derived() {
        std::cout << "Derived ctor called.\n";
    }
    ~Derived() {
        std::cout << "Derived dtor called.\n";
    }
};
Base *foo = new Derived;
delete foo; // compilation error

... as mentioned in your question.

  1. Public virtual destructor - allows a pointer of type Base to a Derived object to be deleted. First the Derived destructor is called, and then the Base destructor is called:
class Base {
public:
    Base() {
        std::cout << "Base ctor called.\n";
    }
    virtual ~Base() {
        std::cout << "Base dtor called.\n";
    }
};

class Derived : public Base {
public:
    Derived() {
        std::cout << "Derived ctor called.\n";
    }
    ~Derived() override {
        std::cout << "Derived dtor called.\n";
    }
};
Base *foo = new Derived;
delete foo;

Output:

Base ctor called.
Derived ctor called.
Derived dtor called.
Base dtor called.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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