peetonn
peetonn

Reputation: 3052

Calling std::function object pointing to the method of deallocated object

Consider this code:

#include <iostream>
#include <functional>

using namespace std;
using namespace std::placeholders; 

typedef function<void(const int&)> SomeFunc;

class X {
public:
    X(string name):name_(name)
    { cout << "ctor " << name_ << endl; }

    ~X()
    {
        cout << "dtor " << name_ << endl; 
        name_ = "empty";
    }

    SomeFunc
    getSomeFunc()
    { return bind(&X::someMethod, this, _1); }

private:
    string name_;

    void
    someMethod(const int& a)
    {
        cout << name_ << " some method with " << a << endl;
    }
};


int main()
{
    SomeFunc f;

    {
        shared_ptr<X> x(new X("Object"));
        f = x->getSomeFunc();
        f(1);
    }

    f(2);

    return 0;
}

Sometimes, output gives me this:

ctor Object
Object some method with 1
dtor Object
empty some method with 2

other times this:

ctor Object
Object some method with 1
dtor Object
 some method with 2

In real world, it would most probably give me crashes once deallocated object tries to access it's attributes. So here is a question - as function does not guarantee holding a reference to the object which method it's pointing to, what is the best practice to avoid crashes when function is called after referenced object was already deallocated?

One of the solutions I might think of - maintain a special flag bool deallocated_ inside object and check it inside the method which might be called after deallocation. However, I suspect, it's not reliable either.

UPDATE (from comments):

The real reason I need this workaround is the library that takes function as a parameter. This library operates asynchronously and I have no control over function objects passed into it. That's why when my object is deallocated, library still can invoke callbacks using originally passed function which leads to a crash.

Upvotes: 2

Views: 626

Answers (4)

peetonn
peetonn

Reputation: 3052

Another solution without using lambda is to derive from enable_shared_from_this and pass shared_from_this in getSomeFunc method:

class X : public enable_shared_from_this<X> {
public:
    X(string name):name_(name)
    { cout << "ctor " << name_ << endl; }

    ~X()
    {
        cout << "dtor " << name_ << endl; 
        name_ = "empty";
    }

    SomeFunc
    getSomeFunc()
    { 
        return bind(&X::someMethod, shared_from_this(), _1); 
    }

private:
    string name_;

    void
    someMethod(const int& a)
    {
        cout << name_ << " some method with " << a << endl;
    }   
};

This, however, will hold object until all callbacks are released.

Upvotes: 0

VinSmile
VinSmile

Reputation: 756

Sulution 1) Using weak_ptr + lambda (almost the same as from b4hand, but it won't force your class beeing alive)

Inherit your class from std::enable_shared_from_this

class X : public enable_shared_from_this<X>

and change getSomeFunc to something like this:

SomeFunc getSomeFunc()
{
    weak_ptr<X> weak = shared_from_this();

    return [weak, this](const int& a){
        shared_ptr<X> shared = weak.lock();

        if (shared)
        {
            this->someMethod(a);
        }
    };
}

output:

ctor Object
Object some method with 1
dtor Object

more details here and here.

Solution 2) A bit of crazy code + lambda
If you can't or don't want to use shared/weak ptrs, you can do it this way:

#include <memory>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <set>

using namespace std;

typedef function<void(const int&)> SomeFunc;

class X {
private:
    static set<X*> _aliveInstanties;
public:
    X(string name) :name_(name)
    {
        _aliveInstanties.insert(this);

        cout << "ctor " << name_ << endl;
    }

    ~X()
    {
        _aliveInstanties.erase(_aliveInstanties.find(this));

        cout << "dtor " << name_ << endl;
        name_ = "empty";
    }

    SomeFunc getSomeFunc()
    {
        return [this](const int& a)
        {
            if (_aliveInstanties.find(this) != _aliveInstanties.end())
            {
                this->someMethod(a);
            }
        };
    }

private:
    string name_;

    void someMethod(const int& a)
    {
        cout << name_ << " some method with " << a << endl;
    }
};

Upvotes: 1

b4hand
b4hand

Reputation: 9770

Your object is being held by a shared_ptr, so you can use a lambda to close over the shared_ptr:

auto func = [ptr](const int &p){ ptr->someMethod(p); };

You'll need to use shared_from_this to get ptr within the class.

Here's a full example that works:

#include <iostream>
#include <functional>
#include <memory>

using namespace std;
using namespace std::placeholders; 

typedef function<void(const int&)> SomeFunc;

class X : public enable_shared_from_this<X> {
public:
    X(string name) : name_(name) {
        cout << "ctor " << name_ << endl;
    }

    ~X() {
        cout << "dtor " << name_ << endl; 
        name_ = "empty";
    }

    SomeFunc getSomeFunc() {
        auto ptr = shared_from_this();
        return [ptr](const int &a){ ptr->someMethod(a); };
    }

private:
    string name_;

    void someMethod(const int& a) {
        cout << name_ << " some method with " << a << endl;
    }
};


int main()
{
    SomeFunc f;

    {
        shared_ptr<X> x(new X("Object"));
        f = x->getSomeFunc();
        f(1);
    }

    f(2);

    return 0;
}

The output looks like this:

ctor Object
Object some method with 1
Object some method with 2
dtor Object

Upvotes: 3

Siqi Lin
Siqi Lin

Reputation: 1257

You can create a class that holds a function pointer and a shared_ptr to the object. The shared_ptr to the object guarantees the object won't be destroyed until your function class is destroyed.

Upvotes: 1

Related Questions