How to enforce that a given resource will always be removed first?

103 Views Asked by At

In my project I have a system of events. You can connect a callback to an event and any time the event is sent, your callback(s) is called.

Upon connecting to an event you get a token. As long as the token is not destroyed, the connection is active:

class A
{

    A()
    {
        event_connection = get_dispatcher().connect(event, std::bind(member_function, this));
    }
    void member_function()
    {
        dummy_instance++; //any action that uses this field
    }
    // changed from shared to unique to avoid confusion
    //std::shared_ptr<event_connection_token> event_connection;
    std::unique_ptr<event_connection_token> event_connection;
    dummy_type dummy_instance;
}

However, a problem arises in the following scenario:

  1. Deconstruction of class A starts
  2. Field dummy_instance is destroyed
  3. Now the event occurs
  4. The callback is called because event_connection wasn’t destroyed yet
  5. The callback tries to access the deallocated memory and the program crashes

Therefore, I need my event_connection_token to always be destroyed before any class members that callback is using. Now if I want 100 other programmers to use this event-callback system it would be unprofessional to expect them to always deallocate event_connection_token first in all classes they ever make. We finally come to the question:

How can I enforce that every client removes event_connection_token before anything else in client class gets destroyed?

I’m looking for either:

  • a clever design that will make sure the token is always removed first without programmers even thinking about it, or
  • a compile-time / run-time check that will let programmers know that they need to modify code so that the token is removed first.

EDIT: The question marked as duplicate does not address my problem. I know the order of destruction of objects, or even explicitly calling .reset() in the destructor will fix my problem. That is however not the solution to my problem. The problem is I don’t want to rely on every developer in the project remembering this rule (as this event-callback system is to be used in many places in the code).

2

There are 2 best solutions below

0
On

You could try excluding the actual callback implementation to a separate class, and then compose it into a "keeper" class:

class ACallback
{
    public:
        void member_function() 
        {
            dummy_instance++; //any action that uses this field
        }
    private:
        dummy_type dummy_instance;
}

class A 
{
    A(ACallback *callback) : callback(callback)
    {
        event_connection = get_dispatcher().connect(event, std::bind(ACallback::member_function, callback));
    }
    ~A()
    {
        // make sure callback will not be used any more
    }
    std::unique_ptr<ACallback> callback;
    std::unique_ptr<event_connection_token> event_connection;
}
1
On

Just swap the declarations

class A
{

    A()
    {
        event_connection = get_dispatcher().connect(event, std::bind(member_function, this));
    }
    void member_function()
    {
        dummy_instance++; //any action that uses this field
    }
    // changed from shared to unique to avoid confusion
    //std::shared_ptr<event_connection_token> event_connection;

    dummy_type dummy_instance;
    std::unique_ptr<event_connection_token> event_connection;
}

the destruction order is in reverse declaration order (because construction happens in declaration order). When destroying an instance now, first the destructor of the instance is called, then event_connection is destroyed an last dummy_instance is destroyed (construction happens in the reverse order).

I think you'll have to live with the fact that there are some rules that has to be obeyed in order to guarantee this if you don't want to go far in preventing them to do "stupid" things (and I don't even think that you can cover all corner cases anyway).

If you cannot require them to put event_connection last, then you have to forbid them from adding it via composition (and even then if you add it at all you'll end up requiring them to explicitely delete the pointer). This would rule out having event_connection in A in the first place, but rather only allow event_connection to have a reference to A, which would work well if you use smart pointer (except that this would mean that the A object would remain as long as event_connection remains).