调用 std::function 对象指向释放对象的方法

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

考虑这段代码:

#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;
}

有时,输出会给我这个:

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

其他时候是这样的:

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

在现实世界中,一旦解除分配的对象试图访问它的属性,它很可能会让我崩溃。 所以这是一个问题 - 由于函数不保证持有对对象的引用,它指向哪个方法,在引用的对象已经被释放后调用函数时避免崩溃的最佳实践是什么?

我可能想到的解决方案之一 - 在对象中维护一个特殊标志 bool deallocated_ 并在释放后可能调用的方法中检查它。但是,我怀疑,它也不可靠。

更新(来自评论):

我需要此解决方法的真正原因是将函数作为参数的库。这个库异步运行,我无法控制传递给它的函数对象。这就是为什么当我的对象被释放时,库仍然可以使用最初传递的函数调用回调,这会导致崩溃。

您可以创建一个包含函数指针的 class 和指向该对象的 shared_ptr。对象的 shared_ptr 保证在你的函数 class 被销毁之前对象不会被销毁。

您的对象由 shared_ptr 持有,因此您可以使用 lambda 来关闭 shared_ptr:

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

您需要使用 shared_from_this 才能在 class 内获得 ptr

这是一个完整的有效示例:

#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;
}

输出如下所示:

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

解决方案 1) 使用 weak_ptr + lambda(与 b4hand 几乎相同,但不会强制你的 class 还活着)

从std::enable_shared_from_this

继承你的class
class X : public enable_shared_from_this<X>

并将 getSomeFunc 更改为如下内容:

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);
        }
    };
}

输出:

ctor Object
Object some method with 1
dtor Object

更多详情here and here

解决方案 2) 有点疯狂的代码 + lambda
如果你不能或不想使用 shared/weak 点,你可以这样做:

#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;
    }
};

另一种不使用 lambda 的解决方案是从 enable_shared_from_this 派生并在 getSomeFunc 方法中传递 shared_from_this

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;
    }   
};

然而,这将保留对象,直到释放所有回调。