首页 > 解决方案 > 可以删除绑定函数时如何安全使用回调

问题描述

在下面的代码中,我们正在创建一个对象,绑定一个函数并在删除对象之后调用它。

这显然会导致分段错误,因为在删除后使用了底层对象。

在为异步数据提供回调的库的上下文中,我们应该如何防止回调函数指向 a nullptr

您可以在 cpp.sh/5ubbg 进行测试

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

class CallbackContainer {
 public:
  std::string data_;
  CallbackContainer(std::string data): data_(data) {}
  ~CallbackContainer() {}
  void rawTest(const std::string& some_data);
};

void CallbackContainer::rawTest(const std::string& some_data) {
  std::cout << data_ << " " << some_data << std::endl;
}

int main(int /* argc */, char const** /* argv */) {
  std::unique_ptr<CallbackContainer> container;
  container.reset(new CallbackContainer("Internal data"));

  auto callback = std::bind(&CallbackContainer::rawTest, container.get(), std::placeholders::_1);
  callback("Before");
  std::cout << &callback << std::endl;
  container.reset();
  std::cout << &callback << std::endl;
  callback("After");
  return 0;
}

回报:

> Internal data Before 
> 0x7178a3bf6570 
> 0x7178a3bf6570 
> Error launching program (Segmentation fault)

标签: c++c++11callback

解决方案


我在使用boost asio时喜欢的方式:

我在使用boost asio时遇到了同样的问题。我们需要注册回调,io_service并且很难实现某种Manager类来管理我们可能创建的对象的生命周期。

因此,我实现了Michael Caisse在 cppcon2016 中提出的一些建议。我开始将shared_ptr对象传递给std::bind.

我曾经延长对象的生命周期,在回调中,您可以决定再次延长对象的生命周期(通过再次注册回调)或让它自动死亡。

std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();
auto func = std::bind(&MyClass::MyMemFunc, this, ptr);
ptr.reset();

在为异步数据提供回调的库的上下文中,我们应该如何防止回调函数指向 nullptr?

我不会说这是最好的解决方案,但通过我上面的方法,您可以检测是否需要在回调中继续进行。

这可能不是有效的方法,但不会导致任何未定义的行为。

void CallbackContainer::rawTest(const std::string& some_data, std::shared<CallbackContainer> ptr) 
{
    if (ptr.use_count() == 1) {
        // We are the only owner of the object.
        return; // and the object dies after this
    }
    std::cout << data_ << " " << some_data << std::endl;
}

编辑:

显示如何使用std::enable_shared_from_this的示例代码:

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


class ABCD: public std::enable_shared_from_this<ABCD> {
public:
    void call_me_anytime()
    {
        std::cout << "Thanks for Calling Me" << std::endl;
    }

public:
    ABCD(void)
    {
        std::cout << "CONSTRUCTOR" << std::endl;
    }

    ~ABCD(void)
    {
        std::cout << "DESTRUCTOR" << std::endl;
    }
};

int main(void)
{
    auto ptr = std::make_shared<ABCD>();
    auto cb = std::bind(&ABCD::call_me_anytime, ptr->shared_from_this());
    ptr.reset();

    std::cout << "RESETING SHARED_PTR" << std::endl;
    std::cout << "CALLING CALLBACK" << std::endl;
    cb();
    std::cout << "RETURNING" << std::endl;
    return 0;
}

输出:

CONSTRUCTOR
RESETING SHARED_PTR
CALLING CALLBACK
Thanks for Calling Me
RETURNING
DESTRUCTOR

推荐阅读