Search code examples
c++polymorphismdestructorvirtual-functions

How to avoid calling a virtual function in destructor when the base class need to know info about the derived when destructing?


Currently I met with the following situation:

I have a base class Base with an void* V member, it may be A*, B*, C* actually, and three (only three, fixed number) catagories of class will derive from Base, each will fill V will different kinds of data. I don't want the user to manually free V in the destructor of every derived class, then Base must determine the real type of V and call the destructor of it. But since virual functions are treated as non-virtual, how can I get the type of V? it will occupies more memory if I added a _type variable additionally (and it will force the user to fill _type for every constructor.

Example here:

class Base {
public:
   void* V;
   virtual ~Base() { /* how to free V? */ }
};

class A : public Base {
public:
   A() : V((void*) new int) {}
   virtual ~A() { /* I don't want to let user 
                     write delete (int*) V in every destructor of 
                     class of type 1, since the user may forget. */
   virtual int type() const { return 1; }
};

class B : public Base {
public:
   B() : V((void*) new double) {}
   
   virtual int type() const { return 2; }
};

class C : public Base {
public:
   C() : V((void*) new int[4]) {}
   
   virtual int type() const { return 3; }
};


My main purpose is to do more things for the users, and don't let them to manually manager the memeory, so for this situation, is there a better way to achieve the same goal? Or is there a workaround to free V properly?


Solution

  • If I understand you correctly, you have a hirarchy of entities where the derived ones(A, B, etc.) each holding different data types.

    The simplest solution would be to remove V from Base and add a specific data member (without a pointer) to each derived class. This way you don't have to deal with deletion of V at all.

    If for some reason you want to separate the main entities from the data they are holding (with the base having access to it), you can create a separate hirarchy of data entities (BaseData, AData, BData etc.). You can also use a smart pointer to avoid the manual memory management.

    This is demonstrated below:

    #include <memory>
    #include <array>
    
    // Hirarchy of data entities:
    
    struct BaseData { 
        virtual ~BaseData() = default; // required for proper destruction of derived classes
    };
    
    struct AData : public BaseData {
        int m_data{ 0 };
    };
    
    struct BData : public BaseData {
        double m_data{ 0 };
    };
    
    struct CData : public BaseData {
        std::array<int, 4> m_data{ 0 };
    };
    
    
    // Hirarchy of main entities:
    
    class Base {
    public:
        std::unique_ptr<BaseData> m_V;
        virtual ~Base() = default; // required for proper destruction of derived classes
    };
    
    class A : public Base {
    public:
        A() { m_V = std::make_unique<AData>(); }
    };
    
    class B : public Base {
    public:
        B() { m_V = std::make_unique<BData>(); }
    };
    
    class C : public Base {
    public:
        C() { m_V = std::make_unique<CData>(); }
    };
    

    Notes:

    1. I used std::array instead of a raw C array.
    2. The data entities are structs only for simplicity.
    3. As mentioned by @Jens in a comment, you can add a consturctor to Base accepting a std::unique_ptr<BaseData> in order to make sure the derived classes will initialize m_V. m_V can also be made private while allowing to access it via an accessor method.