c++ - 可以删除绑定函数时如何安全使用回调
问题描述
在下面的代码中,我们正在创建一个对象,绑定一个函数并在删除对象之后调用它。
这显然会导致分段错误,因为在删除后使用了底层对象。
在为异步数据提供回调的库的上下文中,我们应该如何防止回调函数指向 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)
解决方案
我在使用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
推荐阅读
- azure - 在发布定义 YAML 中查找 Azure DevOps 发布定义服务连接
- docker - Docker 镜像时间戳问题
- github - 作为组织的一部分为 monorepo 发布 Github 包
- c# - 动画开始,但播放器不会在 Unity 中按下按键移动
- node.js - 没有结帐流程的贝宝付款
- python - Python 自动 Outlook 电子邮件:更改发件人或默认回复地址
- c# - 如何在 c# 中将字节数组转换为 HttpPostedFileBase 图像文件?
- r - 循环遍历所有级别并将每个级别值用作定义新变量的过滤器
- azure - 应用服务生产流量进入阶段槽
- ruby - 我正在尝试在 mac 终端上使用命令 gem install rake 安装 rake,但我不断收到此错误