Does a C++ object's destructor get called if there is an outstanding shared_ptr to one of its member functions?

138 Views Asked by At

Let's say we have the following class:

Class MyClass
{
    void func();
    std::shared_ptr<std::function<void()>> getFuncPointer(); // returns pointer to func()
};

Let's say there is an object of some other class (let's call it objectB) that owns a shared_ptr to an object (objectA) of type MyClass. Assume objectB is the only owner/user of objectA and no one else has access to objectA.

What happens in the following sequence:

  1. objectB calls objectA->getFuncPointer() and stores the returned shared_ptr.
  2. objectB calls objectA.reset()

Question: Does objectA's destructor get called?

If objectB did not have the shared_ptr to one of objectA's methods, the answer will obviously be yes.

What I am not sure of is the effect of objectB acquiring a shared_ptr to one of objectA's methods.

If the answer is YES:

If objectA gets destructed, but another thread is still in func() via the function pointer, then what happens if we access a member variable? Seems problematic because objectA is gone along with its members.

If the answer is NO:

Seems problematic since I don't see how a shared_ptr to a member function can possibly keep the entire object alive. The function pointer knows nothing about the encompassing object, right?

Both scenarios seem problematic so that's why I am not sure which is the correct answer.

3

There are 3 best solutions below

6
ruakh On

The answer is YES: an object can and will be deleted even if there's a std::shared_ptr that indirectly relies on it. By creating a std::shared_ptr to a given object (in this case a function), you're keeping that object around as long as it has a shared owner, so you have to promise that the object is valid for that long. If you break that promise, things will go wrong.

It's not really any different from having an object O1 that holds a pointer to an object O2, and then delete-ing O2 while O1 is still around. Even if O1 itself is held via std::shared_ptr, that doesn't preserve O2.


Both scenarios seem problematic so that's why I am not sure which is the correct answer.

Unlike many languages, C++ makes no attempt to be "safe". It's very easy to write code that invokes undefined behavior; an important part of working in C++ is avoiding doing so. std::shared_ptr is a tool to help you write safe code, but it doesn't stop you from writing unsafe code.


What I am not sure of is the effect of objectB acquiring a shared_ptr to one of objectA's methods.

There's actually no such thing as a shared_ptr to a method. In your case, you have a shared_ptr to an instance of std::function<void()>. An instance of std::function holds a callable object (its "target"), but the shared_ptr itself doesn't know anything about that target, doesn't own it, and can't/won't keep it alive.

Additionally, although an instance of std::function can hold a pointer to a member function, that can't be the situation in your case, because a pointer to a member function takes the this pointer as an argument, rather than implicitly depending on a specific instance; so if that were what you have, it would be a std::function<void(MyClass*)> rather than a std::function<void()>, and it wouldn't care if any particular instance of MyClass had been deleted. Rather, your std::function probably holds the result of a lambda expression, and that lambda probably holds a pointer to your instance of MyClass. If so, then that's not an owning pointer, so, again, it won't keep the instance alive.

Some docs you might want to look at:

2
The Wrecker On

Member functions/methods are usually implemented in compilers as global functions with an extra first parameter receiving the instance of the object, so a std::function <void()> 's type would in reality be void (MyClass&)/void (MyClass*) when referencing a method function of MyClass [which is why when you try to create an std::bind for a member function, you pass in the object instance as the first parameter as well].

by returning a shared_ptr of a std::function<void()>, you are basically constructing a shared_ptr containing std::function<void(this)> for &MyClass::func(), which holds no actual ownership over the instance, so the answer is YES, that shared_ptr should not prevent the destruction of the object.

EDIT: just saw your other comment, as i said the methods are functions like other free functions, they reside in the CODE section of the process's memory, they don't occupy DATA memory, so they are not associated with object's lifespan, a single copy of them (under normal scenarios per each translation unit) will be present there whether you create 1 MyClass object or 10000 of MyClass objects, so they are callable in the entirety of runtime of the program

1
selbie On

Most likely yes, it gets deleted as per the details give by @ruakh above.

But there is one possible way for object A to extend it's own lifetime via the getFuncPointer invoked by object B or elsewhere. And that is if the std::function returned by getFuncPointer is capturing the instance itself.

Consider this implementation using enable_shared_from_this

class MyClass : public std::enable_shared_from_this<MyClass>
{
public:
    void func();

    std::shared_ptr<std::function<void()>> getFuncPointer()
    {
        auto spThis = shared_from_this(); // get a shared_ptr to "this"

        auto fn = [spThis]() {   // capture spThis by value increases yet another ref on the shared_ptr
            spThis->func();
        };

        auto result = std::make_shared<std::function<void()>>();
        *result = fn;
        return result;
    }
};


Then if you have this implementation

    auto a = std::make_shared<MyClass>();
    auto spFunc = a->getFuncPointer();
    a.reset();
    (*spFunc)();

The a.reset() call above will reduce the shared_ptr's reference count by 1, but there's still another outstanding reference captured within spFunc. object a will ultimately be deleted when spFunc goes out of scope and releases its captured variables.