C++ problem accessing override pure virtual function after destructor and setting new value

193 Views Asked by At
#include <iostream>
using namespace std;

class Abstract {
protected:
    int* arr;
    int size;
public:
    Abstract(int s = 10) 
    {
        size = s;
        arr = new int[size];
    }
    virtual ~Abstract()
    {
        if (arr) 
        {
            delete[] arr;
        }
        arr = nullptr;
    }
    virtual void foo() = 0;
    void func() { foo(); }
};

class Derived : public Abstract
{
public:
    void newArr()
    {
        Abstract::~Abstract();   // <=> this->~Derived()
        arr = new int[size];
    }
    virtual void foo()override {};

};


int main() {
 
    Derived d;
    d.func();//ok
    d.newArr();//ok
    d.func();//error

    return 0;
}

After calling distractor of abstract class, and setting new values to arr from derived class, next time abstract class use pure virtual function foo, it won't redirect to derived override implementation, and get debug abort() error.

2

There are 2 best solutions below

0
Slava On

As stated in documentation

A destructor is a special member function that is called when the lifetime of an object ends.

Though sometimes it can be called explicitly, those cases are extremely rare and quite complex. Your code express multiple cases of Undefined Behavior due to calling a method of destructed object and calling destructor twice:

The destructor may also be called directly, e.g. to destroy an object that was constructed using placement-new or through an allocator member function such as std::allocator::destroy(), to destroy an object that was constructed through the allocator. Note that calling a destructor directly for an ordinary object, such as a local variable, invokes undefined behavior when the destructor is called again, at the end of scope.

(emphasis is mine)

So simple solution is to have ordinary method, probably protected in your case, which deallocates the array and call it from the destructor and your function:

class Abstract {
protected:
    int* arr;
    int size;

    void deallocate()
    {
        delete[] arr; // Note: delete on nullptr is a Noop
        arr = nullptr;
    }
public:
    Abstract(int s = 10) 
    {
        size = s;
        arr = new int[size];
    }
    virtual ~Abstract()
    {
        deallocate();
    }
    virtual void foo() = 0;
    void func() { foo(); }
};

and then use the same function in the derived class. For cleaner code I would also add protected method void allocate( size_t size ) and move members arr and size to private area of the base class.

Note: I assume you are doing this exercise to better understand how things work in C++ so I answered accordingly. In real code though raw pointers owning memory should be avoided in favor of smart pointers or proper containers to eliminate many potential problems in your code (for example your class is violating The rule of three/five/zero).

0
Andrej Podzimek On

I would strongly recommend not doing anything surprising around destructors and raw pointers:

#include <cstdint>
#include <iostream>
#include <memory>

class Abstract {
 protected:
  size_t size;
  std::unique_ptr<int[]> arr;

 public:
  Abstract(size_t size = 10) : size{size}, arr{std::make_unique<int[]>(size)} {}
  virtual void foo() = 0;
  void func() { foo(); }
};

class Derived : public Abstract {
 public:
  using Abstract::Abstract;
  void newArr() { arr = std::make_unique<int[]>(size); }
  void foo() override {}
};

int main() {
  Derived d;
  d.func();
  d.newArr();
  d.func();

  d = Derived{20};  // frees memory properly!
  d.func();
  d.newArr();
  d.func();
}

When using raw pointers, it is way too easy to forget an operator =() or some such, which leaks memory all over the place. Smart pointers provide the appropriate constructors, destructors and operators.