2

Here's a problem scenario: I've got a C++ object that includes a Cleanup() virtual method that needs to be called just before the object is destroyed. In order to do the right thing, this Cleanup method needs access to the fully-formed object (including subclass data), so I can't just call Cleanup() at the start of my own class's destructor, because by the time my class's destructor is called, the destructors any subclasses have already completed and they may have already freed some data that Cleanup() needed to look at.

The obvious solution would be to require the calling code to manually call my routine before deleting the object:

theObject->Cleanup();
delete theObject;

but that solution is fragile, since sooner or later somebody (probably me) will forget to call Cleanup() and bad things will happen.

Another solution would be to have a "holder" object that implements the pImpl technique to wrap the class, and have the holder object's destructor call Cleanup() on the object before deleting the object; but that solution isn't 100% desirable either, since it makes the class work differently from a standard C++ class and makes it impossible to allocate the object on the stack or statically.

So the question is, is there some clever technique (either in C++ or C++11) that I could use to tell the compiler to automatically call a specified (presumably virtual) method on my object before it calls the first subclass-destructor?

(come to think of it, a similar mechanism for automatically calling an Init() method -- just after the last subclass-constructor completes -- might be handy as well)

Jeremy Friesner
  • 70,199
  • 15
  • 131
  • 234
  • Too short to be worth an answer: What you want exists in Common Lisp Object System with its `:before`, `:after`, and `:around` methods. Some other languages have similar concepts, but not C++ (at least not as far as I know). – David Hammen Jun 01 '12 at 05:01

5 Answers5

6

Lateral thinking: get rid of the Cleanup method, that's what a virtual destructor is for.

The very goal of a virtual destructor is to allow the outmost derived class destructor to be called when delete basepointer; is executed; and as RAII demonstrated, cleanup is the destructor's job.

Now you might argue that you would like an early Cleanup method, to ease the task of the destructor or maybe to reuse the object (kinda like a Reset method in a way). In practice, it rapidly turns into a maintenance nightmare (too easy to forget to clean one field and have state leak from one use to another).

So ask yourself the question: Why not use the destructor for what it's been created for ?

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • Usually I do, but in some cases it's not sufficient (e.g. because the cleanup needs to call a virtual method, or do something else that depends on the subclass information still being available) – Jeremy Friesner Jun 01 '12 at 15:36
  • 1
    The comment still stands, though perhaps the design need be adapted then. If you follow SRP (Single Responsibility Principle) more closely and separate the actual class and its data from the thread, then the class that creates the thread may reference what it is active on (for example through a `shared_ptr`) and ensure they live long enough. In the destructor, join with the thread, and because it holds a `shared_ptr` to the data, they cannot have started their destruction yet. – Matthieu M. Jun 01 '12 at 16:23
3

You can use a twist on the PIMPL wrapper:

class PIMPL
{
    MyDataThatNeedsInitDestroy    object;

  public:
    PIMPL(Atgs a)
       : object(a)
    {
        object.postCreationInit();
    }

    ~PIMP()
    {
        object.preDestructionDestory();
    }

    // All other methods are available via -> operator
    MyDataThatNeedsInitDestroy* operator->() { return &object);
};
Martin York
  • 257,169
  • 86
  • 333
  • 562
  • 1
    You'd probably also want to make MyDataThatNeedsInitDestroy's ctor/dtor private (and use "friend"). Also I'm not sure if this is correctly named PIMPL, since the object needs to be fully defined above this class. – Jon Jun 01 '12 at 04:10
  • YOu take argument by value. What do you think will happen when op tries to feed derived class to this wrapper? – SigTerm Jun 01 '12 at 04:35
  • @SigTerm: Templatise it or make it a pointer. There are a million solutions. But I can not solve any of them with such a generic question all I can do is give a stating point that solves the question asked. – Martin York Jun 01 '12 at 04:41
  • This solution isn't entirely satisfactory, since the onus remains on the calling code to know to do the right thing -- in this case the calling user needs to remember to wrap the object in a PIMPL wrapper, which he might forget to do. Nevertheless I think this is the closest thing to a working solution that has been suggested so far. – Jeremy Friesner Jun 01 '12 at 16:25
  • 1
    @JeremyFriesner: You can force the use of the above wrapper by makeing the constructor private and then the `PIMPL` a friend of the class. THen the only way to create an instance is to wrap it. I also see @Matthieu M. as the better answer and don't understand why you need the separate virtual cleanup(). The destructor can be made virtual and do that work. This technique is usually used with two phase construction not destruction. – Martin York Jun 01 '12 at 16:47
  • Even a virtual base-class destructor can't execute before the subclass's destructor. Usually that's fine, but occasionally there is something that needs to be done before the most-derived-subclass's destructor runs. Matthieu's response is basically "redesign your code to avoid the problem, then you won't need to solve it", which is good pragmatic advice in general, but doesn't actually answer the question at hand. – Jeremy Friesner Jun 01 '12 at 17:13
1

Yes, this can be done with virtual inheritance, because virtual base subobjects are always constructed (and destroyed) by the most-derived type, and the order of construction (and destruction) of base classes is well-defined also.

Of course, you also may make the delete operator private, and provide a Release() member function consisting of Cleanup(); delete this; But that wouldn't help with stack-allocated objects.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • I think he wants Cleanup called before the subclasses destructors run. – Andrew Tomazos Jun 01 '12 at 03:52
  • @Andrew: You use multiple inheritance... one subobject will be destroyed before any of the other inheritance chain. – Ben Voigt Jun 01 '12 at 03:54
  • 1
    No I mean we have class B and class D : B. He has function B::Cleanup he wants called automatically on destruction of D as follows (1) B::Cleanup (2) D::~D (3) B::~B. – Andrew Tomazos Jun 01 '12 at 03:55
  • @Andrew: I submit that there is no legitimate reason to ever want that. If he wants it, it cannot be for good reasons. – Nicol Bolas Jun 01 '12 at 04:02
  • @NicolBolas: Consider a type hierarchy that uses some resource R. Suppose before Rs can be acquired or released a function startr must be called, and endr at the end. Suppose these two functions are expensive, it would make sense to call startr before all destructor chain and endr after it. – Andrew Tomazos Jun 01 '12 at 04:09
  • @NicolBolas: There most certainly is a legitimate reason to ever want that. If not, why would other languages such as CLOS offer it? – David Hammen Jun 01 '12 at 05:02
  • @NicolBolas Here's a concrete usage example -- you can decide if it's legitimate or not. ;^) A base class starts a thread running, the thread is allowed to call various virtual methods on the class. If it's left to the base class's destructor to stop the thread and call pthread_join() (etc), then there is a race condition where the thread is still running while the subclass destructors are destroying the data the thread might access... this causes occasional crashes or data corruption, which is bad. Having pthread_join() et al run *before* the destructors avoids the race condition. – Jeremy Friesner Jun 01 '12 at 05:43
  • 2
    @JeremyFriesner: this use case is flawed. The thread is started prior to the object being fully constructed thus you can have race conditions while constructing the object. You will have to change your design. – Matthieu M. Jun 01 '12 at 06:27
  • @Matthieu The thread is not started before the object is fully constructed -- read my comment again. – Jeremy Friesner Jun 01 '12 at 15:34
  • @Jeremy: You said that "a base class starts a thread running". In the base class constructor, only the base subobject is usable, the object definitely is not "fully constructed". – Ben Voigt Jun 01 '12 at 15:47
  • @Ben I'm aware of that, but my comment doesn't say that the base class constructor starts the thread, it says the base class starts the thread. In particular what I had in mind is that some post-construction call to a base class method starts the thread, and I'm looking for a reliable way to make sure it gets stopped again safely before the base class is destroyed. – Jeremy Friesner Jun 01 '12 at 15:58
0
struct B
{
    void cleanup()
    {
        if (!m_bCleanedUp)
        {
            m_bCleanedUp = true;
            ...
        }
    }

    virtual B::~B()
    {
        assert(m_bCleanedUp);
        ...
    }

    bool m_bCleanedUp = false;
};

struct D : B
{
    D::~D()
    {
        cleanup(); // if D author forgets, assert will fire
        ...
    }
};
Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
-1

Is there a way to get a C++ class to automatically execute a method just before it starts executing the destructor?

You need to use smart pointer (shared_ptr - available in boost or c++11) with custom deleter instead of raw pointers.

typedef shared_ptr<MyClass> MyClassPtr;
class MyClassDeleter{
public:
    void operator()(MyClass* p) const{
        if (p)
            p->cleanup();
        delete p;
    }
};

...

MyClassPtr ptr(new MyClass, MyClassDeleter());

--EDIT--

This will work for dynamically allocated objects. There is no solution (I can think of) for stack-allocated objects, so the reasonable action would be to make constructors private and make friend factory method that returns shared_ptr to constructed object - assuming you really need this mechanism.

SigTerm
  • 26,089
  • 6
  • 66
  • 115
  • Like the `Release()` + private operator delete I mentioned, this only works for dynamically allocated objects. – Ben Voigt Jun 01 '12 at 04:18
  • @BenVoigt: Op wants to delete object. Which means it is dynamically allocated. There is no solution (I can think of) for stack-allocated objects, so the reasonable action would be to make constructors private and make friend factory method that returns shared_ptr to constructed object. – SigTerm Jun 01 '12 at 04:26
  • Really I'd like the solution to work for all allocation types (dynamic, stack, or global)... but perhaps C++ isn't going to give me that :^P – Jeremy Friesner Jun 01 '12 at 06:06