1

Let's say I have the following code:

class BaseMember
{
};

class DerivedMember : public BaseMember
{
};

class Base
{
private:
    BaseMember* mpMember;
protected:
    virtual BaseMember* initializeMember(void)
    {
        return new BaseMember[1];
    }

    virtual void cleanupMember(BaseMember* pMember)
    {
        delete[] pMember;
    }
public:
    Base(void)
        : mpMember(NULL)
    {
    }

    virtual ~Base(void)
    {
        cleanupMember(mpMember);
    }

    BaseMember* getMember(void)
    {
        if(!mpMember)
            mpMember = initializeMember();
        return mpMember;
    }
};

class Derived : public Base
{
protected:
    virtual BaseMember* initializeMember(void)
    {
        return new DerivedMember;
    }

    virtual void cleanupMember(BaseMember* pMember)
    {
        delete pMember;
    }
};

Base and BaseMember are parts of an API and may be subclassed by the user of that API, like it is done via Derived and DerivedMember in the example code.

Base initializes mpBaseMember by a call to it's virtual factory function initializeMember(), so that the derived class can override the factory function to return a DerivedMember instance instead of a BaseMember instance.

However, when calling a virtual function from within a base class constructor, the base implementation and not the derived class override gets called. Therefor I am waiting with the initialization of mpMember until it gets accessed for the first time (which of course implies, that the base class and any derived class, that may could get derived further itself, are not allowed to access that member from inside the constructor).

Now the problem is: Calling a virtual member function from within the base base destructor will result in a call of the base class implementation of that function, not of the derived class override. That means that I can't simply call cleanupMember() from within the base class destructor, as that would call it's base class implementation, which may not be able to correctly cleanup the stuff, that the derived implementation of initializeMember() has initialized. For example the base class and the derived class could use incompatible allocators that may result in undefined behavior when getting mixed (like in the example code - the derived class allocates the member via new, but the base class uses delete[] to deallocate it).

So my question is, how can I solve this problem? What I came up with is: a) the user of the API has to explicitly call some cleanup function before the Derived instance gets destructed. That can likely be forgotten. b) the destructor of the (most) derived class has to call a cleanup function to cleanup stuff which initialization has been triggered by the base class. That feels ugly and not well designed as ownership responsibilities are mixed up: base class triggers allocation, but derived class has to trigger deallocation, which is very counter-intuitive and can't be known by the author of the derived class unless he reads the API documentation thoroughly enough to find that information. I would really like to do this in a more fail-proof way than relying on the users memory or his reliability to thoroughly read the docs.

Are there any alternative approaches?

Note: As the derived classes may not exist at compile time of the base classes, static polymorphism isn't an option here.

Kaiserludi
  • 2,434
  • 2
  • 23
  • 41
  • It's worth mentioning explicitly that you can't call a derived class function from a base class destructor as the derived class will have already been destructed. – Bathsheba Sep 26 '13 at 16:03
  • Your implementation breaks "Resource Acquisition Is Initialization" idiom. I'd rather ask what to do to allocate memory in either base or derived constructor (+1 nevertheless). – cpp Sep 26 '13 at 16:22
  • 1
    What's wrong with smart pointers? – doctorlove Sep 26 '13 at 16:22
  • Why `mpMember` is not protected and is not initialized in derived constructor? – cpp Sep 26 '13 at 16:43
  • @cpp: Where do I break RAII here? – Kaiserludi Sep 26 '13 at 17:03
  • @Kaiserludi, according to RAII, memory should be allocated in constructor and deallocated in destructor, I think – cpp Sep 26 '13 at 17:08
  • @doctorlove: They won't make any difference here. The problem would still exist. In general there is nothing wrong with smart pointers, but for compatibility reasons the code base that I am working on can't use the C++ std lib or C++ 11 and it doesn't yet ship its own smart-pointer implementation (we may add a smart-pointer class in the future). – Kaiserludi Sep 26 '13 at 17:10
  • @Kaiserludi, you may use `auto_ptr` in C++98 – cpp Sep 26 '13 at 17:13
  • @cpp: according to RAII it should be allocated before its first used - that doesn't have to be in the constructor. mpMember is private, so the class can guarantee that it isn't accessed without a call to getMember(), so its RAII-conform to do the allocation of the member in the first call to getMember(). – Kaiserludi Sep 26 '13 at 17:15
  • @cpp: auto_ptr isn't C++ 11, yes, but it's still part of the std lib and asie from that it shows some counter-intuitive behavior in some situations (which is one of the reasons why C++ 11 has introduced new smart pointers). – Kaiserludi Sep 26 '13 at 17:18
  • The base class may get subclassed, but it does not have to. One should be able to use base class instances directly. That's, why I can't rely on the author of the derived class to proper initialize member variables that belong to the base class - there simply won't always be a derived class. Aside from that it feels like bad design if I have to communicate to the derived class author via the base class documentation, that he has to initialize certain base class members in the derived class. It also introduces unnecessary dependencies, if the derived class may directly access member variables – Kaiserludi Sep 26 '13 at 17:28

6 Answers6

1

What about a modification of the factory pattern that would include the cleanup method? Meaning, add a attribute like memberFactory, an instance of a class providing creation, cleanup, as well as access to the members. The virtual initialization method would provide and initialize the right factory, the destructor ~Base would call the cleanup method of the factory and destruct it.

(Well, this is quite far from the factory pattern... Perhaps it is known under another name?)

volferine
  • 372
  • 1
  • 9
  • And where to cleanup the factory itself? – Kaiserludi Sep 26 '13 at 19:54
  • I have thought a bit more about it: I don't need one factory instance per class instance, but just one for the whole class, so get Factory could just return the address of a static function member and I won't have to dynamically allocate the factory and accordingly I don't have to worry about how to clean it up. I have just tried to implement you idea and it actually seems to work just fine (see my own answer for the implementation). – Kaiserludi Sep 26 '13 at 20:17
  • Yes, that is pretty much what I had in mind, plus the neat trick with the static instance. My idea was that the desctructor `~Base` would just do also `delete mpMemberFactory` (no issues with virtual methods here). – volferine Sep 27 '13 at 10:35
  • "My idea was that the desctructor ~Base would just do also delete mpMemberFactory" That would mean, that the Derived class would allocate the factory, but the Base class would deallocate it, which will result in undefined behavior, when they use different allocators. – Kaiserludi Sep 27 '13 at 14:51
1

If you really want to do this sort of thing you can do it like this:

class Base {
    BaseMember* mpMember;

  protected:
    Base(BaseMember *m) : mpMember(m) {}

    virtual void doCleanupMember(BaseMember *m) { delete [] m; }

    void cleanupMember() {
      // This gets called by every destructor and we only want
      // the first call to do anything. Hopefully this all gets inlined.
      if (mpMember) {
        doCleanupMember(pmMember);
        mpMember = nullptr;
      }
    }

  public:
    Base() : mpMember(new BaseMember[1]) { }
    virtual ~Base(void) { cleanupMember(); }
};

class Derived : public Base {
  virtual void doCleanupMember(BaseMember *m) override { delete m; }

  public:
    Derived() : Base(new DerivedMember) {}
    ~Derived() { cleanupMember(); }
};

However there are reasons this is a bad idea.

First is that the member should be owned an exclusively managed by Base. Trying to divide up responsibility for Base's member into the derived classes is complicated and just asking for trouble.

Secondly the ways you're initializing mpMember mean that the member has a different interface depending on who initialized it. Part of the problem you've already run into is that the information on who initialized the member has been destroyed by the type you get to ~Base(). Again, trying to have different interfaces for the same variable is just asking for trouble.

We can at least fix the first problem by using something like shared_ptr which lets up specify a deleter:

class Base {
    std::shared_ptr<BaseMember> mpMember;
  public:
    Base(std::shared_ptr<BaseMember> m) : mpMember(m) { }
    Base() : mpMember(std::make_shared<BaseMember>()) { }
    virtual ~Base() {}
};

class Derived : virtual public Base {     
  public:
    Derived()
      : Base(std::shared_ptr<BaseMember>(new DerivedMember[1],
                                         [](BaseMember *m){delete [] m;} ) {}
};

This only hides the difference in the destruction part of the member's interface. If you had an array of more elements the different users of the member would still have to be able to figure out if mpMember[2] is legal or not.

bames53
  • 86,085
  • 15
  • 179
  • 244
  • +1, in this implementation memory is allocated in constructor and deallocated in destructor – cpp Sep 26 '13 at 17:45
  • This solution won't work, if the author of Derived mistakenly lets its constructor(s) call the default constructor of Base and I can't simply make the default constructor of Base private, because that would also prevent its usage in other places, so this relies a lot on communicating to the Derived author that he has to do something special. Still this would be the best option up to now, if I could use std::shared_ptr in my code base. – Kaiserludi Sep 26 '13 at 19:11
  • @Kaiserludi "communicating to the Derived author that he has to do something special" That's pretty much required to ever use inheritance correctly. It's one reason composition is preferred over inheritance. However, I don't see the problem here with some derived class calling the default Base constructor. `class Derived : virtual public Base { Derived() : Base() {} };` still correctly cleans up the member. – bames53 Sep 27 '13 at 04:44
0

Never never never call virtual methods in constructor/destructor because it makes strange results (compiler makes dark and weird things you can't see).

Destructor calling order is child and then parent

You can do like this (but there is probalby a better way) :

private:
    // private destructor for prevent of manual "delete"
    ~Base() {}

public:
    // call this instead use a manual "delete"
    virtual void deleteMe()
    {
        cleanupMember(mpMember);
        delete this; // commit suicide
    }

More infos about suicide : https://stackoverflow.com/a/3150965/1529139 and http://www.parashift.com/c++-faq-lite/delete-this.html

PS : Why destructor is virtual ?

Community
  • 1
  • 1
56ka
  • 1,463
  • 1
  • 21
  • 37
  • "Never never never call virtual methods in constructor/destructor because it makes strange results" That's absolutely clear. Otherwise I wouldn't have asked the question in the first place, as the whole question is "I know, I can't call these virtual functions in the constructor and destructor, I can easily avoid the call in the constructor, but how to avoid the call by the destructor and still guarantee that all resources get freed?" – Kaiserludi Sep 26 '13 at 16:30
  • "(destructor is virtual !?)" Yes, it is. What is the intention of your question? – Kaiserludi Sep 26 '13 at 16:31
  • It was a general question, I would write "?" instead "!?" ;) – 56ka Sep 26 '13 at 16:32
  • Destructor should always be virtual. Otherwise derived destructor would not be called in case when base class pointer points to derived object. – cpp Sep 26 '13 at 16:48
  • http://www.parashift.com/c++-faq/virtual-dtors.html - I already need a virtual table anyway because of the other virtual functions - This way code like Base* p = new Derived; delete p; doesn't lead into undefined behavior (http://stackoverflow.com/a/461224/404734) – Kaiserludi Sep 26 '13 at 16:55
  • @cpp: Well, not absolutely "always". If a class doesn't have any virtual functions, is not intended to be sub-classed and one should be able to use it in scenarios that are that performance critical that a virtual table means unacceptable overhead, then one may be better of with declaring it non-virtual. – Kaiserludi Sep 26 '13 at 16:59
  • If your class is polymorphic (i.e. it's designed to be inherited from) then the destructor should either be public and virtual or protected and non-virtual. Not all classes need polymorphic deletion. – Simple Sep 26 '13 at 17:06
0

First of all, you must use RAII idiom when developing in C++. You must free all your resources in destructor, of course if you don't wish to fight with memory leaks.

You can create some cleanupMember() function, but then you should check your resources in destructor and free them if they are not deleted (as cleanupMember can be never called, for example because of an exception). So add destructor to your derived class:

virtual ~Derived()
{
    Derived::cleanupMember(mpMember);
}

and manage the member pointer in the class itself.

I also recommend you to use smart pointers here.

Ivan
  • 2,007
  • 11
  • 15
  • The problem is: If in the base class destructor I detect that cleanupMember() has not been called yet, then it's already to late to do the cleanup work of the derived class. "I also recommend you to use smart pointers here." How would they make any difference here? A smart pointer that is a member of the base class would deallocate its payload when the base class destructor gets called, but I then its already to late to call the derived implementation of a virtual function as the derived class constructor has already been called. – Kaiserludi Sep 26 '13 at 16:39
  • Again, you MUST NOT call any virtual function from `~Base`. It must work in RAII rules, so when you get into `~Base`, then `~Derived` was already called, and resource was freed. Smart pointers just give you a chance to sleep at night, not debug memory leaks. – Ivan Sep 26 '13 at 16:44
0

Let mpMember be protected and let it be initialized in derived class constructor and deallocated in derived destructor.

cpp
  • 3,743
  • 3
  • 24
  • 38
0

Inspired by the ideas from https://stackoverflow.com/a/19033431/404734 I have come up with a working solution :-)

class BaseMember
{
};

class DerivedMember : public BaseMember
{
};

class BaseMemberFactory
{
public:
    virtual ~BaseMemberFactory(void);

    virtual BaseMember* createMember(void)
    {
        return new BaseMember[1];
    }

    virtual void destroyMember(BaseMember* pMember)
    {
        delete[] pMember;
    }
};

class DerivedMemberFactory : public BaseMemberFactory
{
    virtual BaseMember* createMember(void)
    {
        return new DerivedMember;
    }

    virtual void destroyMember(BaseMember* pMember)
    {
        delete pMember;
    }
};

class Base
{
private:
    BaseMemberFactory* mpMemberFactory;
    BaseMember* mpMember;
protected:
    virtual BaseMemberFactory* getMemberFactory(void)
    {
        static BaseMemberFactory fac;
        return &fac;
    }
public:
    Base(void)
        : mpMember(NULL)
    {
    }

    virtual ~Base(void)
    {
        mpMemberFactory->destroyMember(mpMember);
    }

    BaseMember* getMember(void)
    {
        if(!mpMember)
        {
            mpMemberFactory = getMemberFactory();
            mpMember = mpMemberFactory->createMember();
        }
        return mpMember;
    }
};

class Derived : public Base
{
protected:
    virtual BaseMemberFactory* getMemberFactory(void)
    {
        static DerivedMemberFactory fac;
        return &fac;
    }
};
Community
  • 1
  • 1
Kaiserludi
  • 2,434
  • 2
  • 23
  • 41