views:

341

answers:

3

Hi!

I have found that boost::signals2 uses sort of a lazy deletion of connected slots, which makes it difficult to use connections as something that manages lifetimes of objects. I am looking for a way to force slots to be deleted directly when disconnected. Any ideas on how to work around the problem by designing my code differently are also appreciated!

This is my scenario: I have a Command class responsible for doing something that takes time asynchronously, looking something like this (simplified):

class ActualWorker {
public:
    boost::signals2<void ()> OnWorkComplete;
};

class Command : boost::enable_shared_from_this<Command> {
public:
    ...

    void Execute() {
        m_WorkerConnection = m_MyWorker.OnWorkDone.connect(boost::bind(&Command::Handle_OnWorkComplete, shared_from_this());

        // launch asynchronous work here and return
    }

    boost::signals2<void ()> OnComplete;

private:
    void Handle_OnWorkComplete() {
        // get a shared_ptr to ourselves to make sure that we live through
        // this function but don't keep ourselves alive if an exception occurs.
        shared_ptr<Command> me = shared_from_this();

        // Disconnect from the signal, ideally deleting the slot object
        m_WorkerConnection.disconnect();

        OnComplete();

        // the shared_ptr now goes out of scope, ideally deleting this
    }

    ActualWorker m_MyWorker;
    boost::signals2::connection m_WorkerConnection;
};

The class is invoked about like this:

...
boost::shared_ptr<Command> cmd(new Command);
cmd->OnComplete.connect( foo );
cmd->Execute();
// now go do something else, forget all about the cmd variable etcetera.

the Command class keeps itself alive by getting a shared_ptr to itself which is bound to the ActualWorker signal using boost::bind.

When the worker completes, the handler in Command is invoked. Now, since I would like the Command object to be destroyed, I disconnect from the signal as can be seen in the code above. The problem is that the actual slot object is not deleted when disconnected, it is only marked as invalid and then deleted at a later time. This in turn appears to depend on the signal to fire again, which it doesn't do in my case, leading to the slot never expiring. The boost::bind object thus never goes out of scope, holding a shared_ptr to my object that will never get deleted.

I can work around this by binding using the this pointer instead of a shared_ptr and then keeping my object alive using a member shared_ptr which I then release in the handler function, but it kind of makes the design feel a bit overcomplicated. Is there a way to force signals2 to delete the slot when disconnecting? Or is there something else I could do to simplify the design?

Any comments are appreciated!

+1  A: 

Is the behaviour any more strict with a scoped_connection?

So, rather than:

void Execute() {
    m_WorkerConnection = m_MyWorker.OnWorkDone.connect(boost::bind
        (&Command::Handle_OnWorkComplete, shared_from_this());

    // launch asynchronous work here and return
}

...

boost::signals2::connection m_WorkerConnection;

Instead using:

void Execute() {
    boost::signals2::scoped_connection m_WorkerConnection
        (m_MyWorker.OnWorkDone.connect(boost::bind
        (&Command::Handle_OnWorkComplete, shared_from_this()));

    // launch asynchronous work here and return
}   // connection falls out of scope

(copy-constructed from a boost::signals2::connection)

I've not used any sort of signalling so it's more of a guess than anything else, but following Execute() you wouldn't need to disconnect(), since scoped_connection handles it for you. That's more of a 'simplify the design' rather than actually solving your problem. But it may mean that you can Execute() and then immediately ~Command() (or delete the shared_ptr).

Hope that helps.

EDIT: And by Execute() then immediately ~Command() I obviously mean from outside your Command object. When you construct the Command to execute it, you should then be able to say:

cmd->Execute();
delete cmd;

Or similar.

darvids0n
Disconnecting at the end of the execute function defeats the purpose of connecting at all - i.e I won't get a callback when the asynchronous work is completed. So your suggestion isn't really viable.
villintehaspam
+1  A: 

boost::signals2 does clean up the slots during connect/invoke.

So if all the slots disconnect themselves from the signal, invoking the signal a second time will not call anything but it should clean up the slots.

To answer your comment, yes, invoking the signal again is not safe if there are be other slots connected, as they will be invoked again. In that case I suggest you go the other way around and connect a dummy slot, then disconnect it when your "real" slot is invoked. Connecting another slot will clean up stale connections, so your slot should be released.

Just make sure that you don't keep any references that need freeing in the dummy slot, or you're back where you started.

Dan Berindei
This is exactly what I wrote in the question, that I would like to avoid. I'm sorry if the question text doesn't convey this properly. Anyway, the signal isn't invoked again - and I assume that you are not suggesting to invoke it a second time as a means to delete the object?
villintehaspam
That's exactly what I was suggesting, sorry for the weird wording. boost::signals2 "garbage collects" the disconnected slots during an invoke, so if you invoke it again after you disconnected it should delete your object.
Dan Berindei
Sorry for a late reply, been out of reach for a while. However, your suggestion would require that I know that there is no other slot connected to the signal, otherwise these will be invoked multiple times, which may or may not be safe.
villintehaspam
+1  A: 

This is an incredibly annoying aspect of boost::signals2.

The approach I took to resolve it is to store the signal in a scoped_ptr, and when I want to force disconnection of all slots, I delete the signal. This only works in cases when you want to forcefully disconnect all connections to a signal.

Kranar