如何检查类的“this”指针在回调函数中是否仍然有效?

How could I check if "this" pointer of a class is still valid in a callback function?

提问人:Ryan 提问时间:10/11/2023 最后编辑:Ryan 更新时间:10/20/2023 访问量:145

问:

class myClass
{
public:
    void fun()
    {
         async_do( [this](int j)
                        {
                            i+=j;
                        } );
    }

private:
    int i=9;
}

int main()
{
    std::shared_ptr<myClass> obj = std::make_shared<myClass>();
    obj->fun();
    obj->reset();
    return 0;
}

上面的代码,当我们调用时,它会调用,会立即返回。异步操作完成后,将调用 lambda 函数。但是,当此异步操作仍在运行时,对象可能会被销毁。调用 lambda 时,该对象不再存在。在这种情况下,我应该在这个回调函数中做什么?
谢谢你的帮助。
fun()async_do()async_do()

C++ 异步 Lambda 并发 回调

评论

4赞 Pepijn Kramer 10/11/2023
您不必检查这一点,它应该在设计上是有效的。async_do应按值捕获shared_ptr,以延长对象的生存期,只要线程需要访问它。
2赞 HolyBlackCat 10/11/2023
enable_shared_from_this
5赞 user17732522 10/11/2023
至于检查是否仍然有效:从根本上来说,检查指针在 C++ 中是否有效是不可能的(除了测试它是否为空指针值)。这是用户必须在外部跟踪的东西,这就是为什么存在这样的类型来帮助它们在适用的地方。thisshared_ptr
8赞 Jerry Coffin 10/11/2023
“医生,我这样做很痛!”“那就别那样做。”不要试图检查是否有效。设计代码,使其有效。this
2赞 Louis Go 10/11/2023
这是一个可以使用weak_from_this的地方。

答:

2赞 Louis Go 10/11/2023 #1

在这种情况下,weak_from_this很有用。

用例:当您需要创建回调,但回调的调用者不知道回调的生存期,并且设计允许在调用回调之前进行 destructed。thisthis

两者的区别在于,它不会使实例保持活动状态,而只是在需要时才尝试访问它。需要注意的是,捕获可能会延长实例的生命周期,直到释放回调。它可能不适合所有用例。shared_from_this()weak_from_this()std::shared_ptr

例如: 只需要调用最新的实例,而不是保留所有实例,并且不想保留其他实例。

现场演示

#include <fmt/core.h>
#include <fmt/format.h>
#include <functional>
#include <memory>

using Callback = std::function<void(int)>;

class myClass : public std::enable_shared_from_this<myClass>
{
public:
    Callback createWeakCallback()
    {
        return [weak_ref = weak_from_this()](int j) {
                auto strong_ref = weak_ref.lock();
                if (nullptr == strong_ref){
                    fmt::print("This is dead.\n");
                    return;
                }
                // use `strong_ref` as `this`
                strong_ref->add(j);
            };
    }

    Callback createStrongCallback()
    {
        return [shared = shared_from_this()](int j) {
                shared->add(j);
            };
    }

    myClass(int val): i(val){}
    ~myClass(){
        fmt::print("dtor, i:{}\n", i);
    }

    void add(int j){ 
        i+=j;
        fmt::print("i: {}\n", i);
    }

private:
    int i=9;
};

int main()
{
    std::shared_ptr<myClass> obj = std::make_shared<myClass>(0);
    Callback cb = obj->createWeakCallback();
    cb(6);
    obj.reset();
    cb(5);

    { // Scope of strongCb
    Callback strongCb;
    {
        std::shared_ptr<myClass> obj = std::make_shared<myClass>(100);
        strongCb = obj->createStrongCallback();
        strongCb(10); // <-- still alive
        fmt::print("End of obj first std::shraed_ptr scope\n");
    }
        fmt::print("End of strongCb\n");
    }   // <-- obj get destructed here
    return 0;
}

输出:

i: 6
dtor, i:6
This is dead.
i: 110
End of obj first std::shraed_ptr scope
End of strongCb
dtor, i:110
0赞 Jorge 10/11/2023 #2

您可以捕获对象而不是原始指针,以确保在调用 lambda 回调时对象仍处于活动状态。回调将保存对对象的引用,确保在回调完成之前不会销毁对象。std::shared_ptr

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

void async_do(std::function<void(int)> callback) {
    // async callback
}

class myClass : public std::enable_shared_from_this<myClass>
{
public:
    void fun()
    {
         auto self = shared_from_this();
         async_do([self](int j) {
                            self->i += j;
                        });
    }

private:
    int i = 9;
};

int main()
{
    std::shared_ptr<myClass> obj = std::make_shared<myClass>();
    obj->fun();
    obj.reset(); // This will not destroy the object if the callback hasn't finished yet

    return 0;
}

如果你不能使用智能指针,我建议你实现一个最低限度工作的共享指针。方法应该是类似的。

您还可以通过为 myClass 的每个实例分配一个唯一的 ID 来使用基于令牌的系统。然后,使用静态映射来保存对类的活动实例的引用,并在调用 fun() 时,为异步操作提供 ID。在回调中,检查 ID 是否仍然存在。如果是这样,则该对象仍处于活动状态,您可以继续。下面是一个最小的示例:

#include <iostream>
#include <functional>
#include <unordered_map>

void async_do(std::function<void(int)> callback) {
    // ...
}

class myClass {
public:
    myClass() {
        id = ++lastId;
        activeInstances[id] = this;
    }

    ~myClass() {
        activeInstances.erase(id);
    }

    void fun() {
        int localId = id;
        async_do([localId](int j) {
            if (activeInstances.count(localId)) {
                activeInstances.at(localId) += j;
            }
        });
    }

private:
    int i = 9;
    int id;
    static int lastId;
    static std::unordered_map<int, myClass*> activeInstances;
};

int myClass::lastId = 0;
std::unordered_map<int, myClass*> myClass::activeInstances;

int main() {
    {
        myClass obj;
        obj.fun();
        // obj will be destroyed at the end of this scope
    }

    // ...

    return 0;
}