How can I guarantee an object's lifespan matches the duration of a member function?

StackOverflow https://stackoverflow.com/questions/9996666

  •  28-05-2021
  •  | 
  •  

Question

I've been making use of callbacks to reduce coupling between some C++ classes. To define terms: I'll call the class making the callbacks the caller, and the class receiving the callback the callee. Typically (but not necessarily), the callee will own the caller. By design, the caller has no knowledge of the callee.

I'm running into an issue relating to the lifespan of the caller object: It has no guarantee that it will still be alive after making any arbitrary callback. Take this basic example:

void caller::f()
{
    /* Some work */
    if (...)
    {
        /* [1] Execute callback */
        _callee->callback(this);
    }
    /* [2] Some more work */
}

Say that the callee has dynamically allocated the caller, and has registered for the callback specifically to wait for a certain condition to occur. When it does, the callee will delete the caller from within the callback at [1]. If that's the case, control will then return to caller::f, but this will have been deleted, and any code at [2] will more than likely crash.

In the general case, the caller can't assume anything about the callee. It doesn't know if the callee owns this, or if it might deallocate this, so I would need some general means of preventing deallocation for the scope of the caller's member function.

I believe a possible solution revolves around boost::shared_ptrs and enable_shared_from_this, though I've never used it. Since these callbacks are running very frequently (40+ times per second) on mobile devices with limited processing power, I'm also worried about the overhead of creating and passing out that many shared_ptrs.

Delegation is a pretty common pattern in Objective-C. I'm far less familiar with common C++ design patterns. Is there any quick-and-easy fix for this issue? If not, how would this design typically be accomplished in C++?

Was it helpful?

Solution

Go ahead and use the shared pointer, though if possible use std::shared_ptr instead of boost::shared_ptr. It's in the (current) standard library, so no need to add an unnecessary boost dependency. If you're already using boost, then that's fine too.

You didn't specify what sort of mobile device you're talking about, but the processors in modern smartphones run at hundreds or thousands of megahertz, and even low-power phones often run Java programs (with garbage collection) just fine. Shared pointers are basically reference counted. It's not a resource-intensive activity.

If your device is able to actually run the callback more than 40 times per second, I doubt it will have any trouble with shared pointers. Don't prematurely optimize for execution speed. DO prematurely optimize for safety and sanity.

OTHER TIPS

When the callee deletes caller, caller's destructor is called. It is there that you should make sure f has finished.

I am guessing f is a thread, so easiest solution would be:

thread:

running = true;
while (!must_exit)
    /* do something */

destuctor:

thread->must_exit = true;
while (thread->running)
    sleep(a_little);
/* continue with destruction */

If f is not a thread, the same principle can apply where f makes its object (and through that its destructor) know when it is running and when not.


If you don't want to go with a destructor approach, you can still implement this functionality through a function that the callee calls, telling f to never run again and wait until it stops. Then the callee continues with deletion of caller.

So something like this:

void caller::f()
{
    if (being_deleted)
        return;
    running = true;
    /* Some work */
    if (...)
    {
        /* [1] Execute callback */
        _callee->callback(this);
    }
    /* [2] Some more work */
    running = false;
}

void caller::make_f_stop()
{
    being_deleted = true;
    while (running)
        sleep(a_little);
}

The usual way of bypassing code that you can't execute anymore is to throw an exception. This should be done by the callee at the point where it would normally return to the caller, after it has deleted the caller. The exception would be caught in the caller code at the end of the function.

I can't say I like this solution, but I think that stems from the unusual situation of the callee owning the caller.

I don't know how smart pointers would help since there's nobody to own the second copy of the pointer.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top