How to prevent an object from being deleted via a pointer to its parent type?

Viewed 135
struct A
{};

struct B : A
{
    operator A() const = delete; // not work
};

int main()
{
    B* p_derived = new B();
    delete p_derived; // ok

    // How to make the following two lines illegal?
    A* p_base = new B();
    delete p_base; 
}

A's definition cannot be changed and its destructor is not virtual; so B's object shouldn't be deleted by a pointer to A.

I can't use struct B : protected A {};, because I want B to inherit all public members of A and keep them still public.

In C++'s mixin patterns, this is a common problem:

This is ok unless the mixin class is deleted polymorphically through a pointer to the original class.

My question:

How to prevent an object from being deleted via a pointer to its parent type?

2 Answers

Make A's destructor protected then the sub-class can access it to correctly destory objects of type B but it's not accessable for elsewhere.

struct A
{
protected:
   ~A() {}
};

struct B :  A
{
};

int main()
{
    A* p_base = new B();
    delete p_base; // This is now an error
}

If you really can't change A then you could do this:

struct A
{
};

struct AA : A
{
protected:
   ~AA() {}
};

struct B :  AA
{
};

int main()
{
    AA* p_base = new B();
    delete p_base; // How to make this a compile-time error?
}

This doesn't work if you delete through A rather than AA though.

Depending on the nature of your problem you could do '#define A AA' or some other similar ugly hack to make this work with an existing codebase.

struct A
{
};

struct AA : A
{
protected:
   ~AA() {}
};

#define A AA
struct B :  A
{
};

int main()
{
    A* p_base = new B();
    delete p_base; // How to make this a compile-time error?
}

The short answer is that you can't.

Consider a function like this

// definition of struct A

void func(A *p)
{
    delete p;
}

and a caller in another compilation unit

// definitions of struct A and B as in question

void func(A *);

void caller()
{
     func(new B);
}

In this case, the code in func() has no visibility at all of type B, let alone the potential that the pointer p it receives actually points at a B. Since func() does delete p, the function caller() has caused func() to have undefined behaviour.

The real solution, in this case, is that B should not inherit from A. There is already a strong hint of that in the fact that A does not have a virtual destructor. That will prevent caller() calling func() with a B *.

If you want B to provide the same interface as A, then you need to use composition, and define all member functions of B so they forward to a contained A. For example;

 struct B
 {
      B() : a() {};    

      void some_member(some_type arg)
      {
          a.some_member(arg);      // assume A has this member that accepts these arguments
      }

      private:

         A a;
 };

There are, obviously, options available if the struct A can be modified, but the question has explicitly stated a use case where that is not possible.

Related