Search code examples
c++qtpointersqmlobject-lifetime

block object usage after deletion of "this" pointer


I'm working on an application that has the following problem. Basically there are a lot of object that implicitly might destroy themselves.

void Foo::func()
{
    ...
    Bar b;
    b.func2();
}

Here the func2 might destroy the foo object calling it. Since it is not very obvious that this might happen I would like to make sure that no member variables of the foo object can be accessed after this call, since I cannot guarantee they are still valid. If this call to b is the last call I am perfectly fine, but since I am not the only one working on this project and the destruction is not obvious I would like to implement something to block usage this after these calls. Is there a way to implement this without completely refactoring the current design (since it is use extensively thoughout the project)?

Some background info:

It is a Qt UI application that uses QML to keep track of a stack of "screens" (a screen is a QML + its corresponding C++ model). The Bar class in my example is the 'stack manager' which controls the lifetime of the screens. It is a singleton (I know). The Foo class is a C++ model for a specific QML. The function Foo::func() is a function that can be called either from user input in the QML or another system event. The function normally processes the event, but occasionaly it can tell the screen manager to delete one or more screen(s), which in turn deletes the model corresponding to that screen (which might be the calling model).


Solution

  • Serious design problem ?

    The this pointer can't be blocked. As with all raw pointers, you must be sure that it's valid before dereferencing it.

    But that's not all: what if the Foo that was calling fun2() was a local object of automatic storage duration ?

    • If you'd destroy the object in the function call by invoking its destructor, you'd have to ensure that there's still a valid object at the same place: otherwise the automatic invocation of the destructor when leaving the block in which the object was defined might lead to undefined behavior.
    • If you'd delete the object instead of just destroying it, it would be undefined behavior in any case.

    Alternative design with smart pointers

    One way out of this situation would be to create all the Foo and Bar objects using factories (making sure that the constructor is not public), and let these factories return shared_ptr.

    This has the advantage that if an object is not used anymore, it will destroy itself. That's a lot cleaner than managing destruction manually.

    You'd face however several challenges:

    • You'd have to refactor all the code to replace raw pointers by smart pointers
    • In order to avoid endless live due to cross referencing (two objects keeping each a slart pointer to the other), you'd need to figure out when to use shared_ptr (if the pointer must ensure the object is still alive) and when to use weak_ptr (i.e. pointer that doesn't contribute to keeping objects alive).

    Alternative design with states

    Another alternative would be to use states to make the difference between an active object that can be used, and a deceased objet that shall no longer be used. So instead of deleting the object, you'd call a state change function that would clean/reset the object.

    This can be implemented :

    • either very nicely with a state design pattern (but this could require some extensive refactoring),
    • or with a simple flag. It would then be checked at the beginning of each member function, and before accessing data members that follows a call that could potentially change the object's state. This is still error prone and requires analysis, but would require mostly "local" changes and gives thus more flexibility for refactoring.

    The advantage of this approach is that the state change function will be very simple (most of the things you have currently in the destructor, but avoiding they could be done by accident a second time) and make the tricky part very visible.

    The difficulty will be to ensure that the deceased objects are cleaned for good. So you need to address the ownership issue that was already mentioned earlier, and make sure the objects are not deleted in the middle of their actions.