std::bind中传入的实参变量的生命周期不能短于生成的可调用对象的生命周期

本文主要是介绍std::bind中传入的实参变量的生命周期不能短于生成的可调用对象的生命周期,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在使用bind生成可调用对象时,bind的中传入的实参变量的生命周期不能短于生成的可调用对象的生命周期。

错误示例

一个错误示例:给 bind 传递的参数为引用类型,然而该引用变量的生命周期短于生成的可调用对象的生命周期,从而导致了在调用 bind 生成的可调用对象时,该引用变量变成了悬垂引用。

#include <memory>
#include <functional>
#include <string>
#include <iostream>
#include <thread>using Func = std::function<void()>;
void queueInLoop(Func);void runInLoop(Func func)
{queueInLoop(std::move(func));
}void queueInLoop(Func func)
{std::this_thread::sleep_for(std::chrono::seconds(3));func();
}void insertInLoop(std::unique_ptr<std::string> &str)
{std::cout << "insertInLoop" << std::endl;std::cout << *str << std::endl;
}void func(std::unique_ptr<std::string>& pstr)
{runInLoop(std::move(std::bind(&insertInLoop, std::ref(pstr))));
}int main()
{std::unique_ptr<std::string> str(new std::string("hello"));std::thread t(func, std::ref(str));str.reset();  // 令其管理的对象销毁t.join();
}

示例说明:

  • main 函数中,创建了一个线程,将 main 中的 str 变量传递给引用传递给创建的线程。
  • 调用 std::thread 创建并启动一个线程,然后调用了 str.reset() ,模拟 str 所管理对象的销毁,模拟出悬垂引用。
  • 在新创建的线程中(线程主函数 func),使用 std::bindinsertInLoop 与 传入funcpstr 进行绑定,这里仍然是引用传递。然后将新生成的可调用对象传入给 runInLoop 函数,这里的参数传递方式是值移动。
  • runInLoop 函数中进行函数嵌套调用(至于这里为什么要进行函数嵌套调用,简单解释一下。这个 demo 来自muduo网络库,是我在重写过程中遇到的一个 bug。在这个 demo 中,只需重点关注变量生命周期问题导致悬垂引用)。在 queueInLoop 函数中,令其睡眠3s,模拟延长 std::bind 生成的可调用对象的调用,从而模拟出悬垂引用的现象。
  • queueInLoop 函数中传入的可调用对象被执行,传入的可调用对象即 func 中的 std::bind(&insertInLoop, std::ref(pstr))func 的调用等价于 insertInLoop(str)strmain 函数中传入的 变量。然而此时 main 中的 str 已销毁了其管理的对象(str.reset()),func 可调用对象中执行的 std::cout << *str << std::endl; 语句中,*str 访问了一个悬垂引用,导致程序出错。

下面的示例对上述程序添加了打印输出,可以运行查看悬垂引出产生的时机。

#include <memory>
#include <functional>
#include <string>
#include <iostream>
#include <thread>using Func = std::function<void()>;
void queueInLoop(Func);void runInLoop(Func func)
{queueInLoop(std::move(func));
}void queueInLoop(Func func)
{std::this_thread::sleep_for(std::chrono::seconds(3));func();
}void insertInLoop(std::unique_ptr<std::string> &str)
{std::cout << "insertInLoop" << std::endl;std::cout << *str << std::endl;
}void insertInLoop2(std::string &str)
{std::cout << str << std::endl;
}void func(std::unique_ptr<std::string>& pstr)
{std::this_thread::sleep_for(std::chrono::seconds(2));if (pstr) {std::cout << "pstr is valid." << std::endl;}else {std::cout << "pstr is invalid!" << std::endl;std::cout << "cout *pstr will be segmentation fault!" << std::endl;std::cout << *pstr << std::endl;}std::cout << "before runInLoop" << std::endl;runInLoop(std::move(std::bind(&insertInLoop, std::ref(pstr))));
}int main()
{std::unique_ptr<std::string> str(new std::string("hello"));std::thread t(func, std::ref(str));str.reset();t.join();
}

解决办法

在给出上述问题的解决方法之前,先把可能会出现上述情况的场景总结如下:

我们需要在线程A中在堆上申请一块内存资源,并且可能会传递给线程B使用,并将其生命周期交给线程B管理,且我们希望使用 unique_ptr 来替换原始指针管理内存资源。对着跨线程使用的场景,如上面的示例所示,我们需要使用 bind 将这块动态内存进行绑定以生成一个可调用对象,然后传递给另一个线程调用,这就可能出现上述示例中的悬垂引用的情况了。

我给出的一种解决思路是,在跨线程调用时,比如在线程A中,使用原始指针进行创建,然后使用原始指针以值拷贝的形式跨线程传递给B,在B线程中在使用 unique_ptr 接管这块内存,从而避免线程A中 unique_ptr 管理的内存提前释放的问题。修改后的代码示例如下:

#include <memory>
#include <functional>
#include <string>
#include <iostream>
#include <thread>using Func = std::function<void()>;
void queueInLoop(Func);void runInLoop(Func func)
{queueInLoop(std::move(func));
}void queueInLoop(Func func)
{std::this_thread::sleep_for(std::chrono::seconds(3));func();
}void insertInLoop(std::string *str)
{// 假设 insertInLoop 是在线程B中被调用// 在线程中使用 unique_ptr 接管线程A传入的原始指针std::unique_ptr<std::string> pstr(str);std::cout << "insertInLoop" << std::endl;std::cout << *pstr << std::endl;
}void func(std::string* pstr)
{std::this_thread::sleep_for(std::chrono::seconds(2));runInLoop(std::bind(&insertInLoop, pstr));
}// 线程A
int main()
{std::string* str = new std::string("hello");std::thread t(func, str);t.join();
}

这篇关于std::bind中传入的实参变量的生命周期不能短于生成的可调用对象的生命周期的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/640065

相关文章

Django调用外部Python程序的完整项目实战

《Django调用外部Python程序的完整项目实战》Django是一个强大的PythonWeb框架,它的设计理念简洁优雅,:本文主要介绍Django调用外部Python程序的完整项目实战,文中通... 目录一、为什么 Django 需要调用外部 python 程序二、三种常见的调用方式方式 1:直接 im

Java调用DeepSeek API的8个高频坑与解决方法

《Java调用DeepSeekAPI的8个高频坑与解决方法》现在大模型开发特别火,DeepSeek因为中文理解好、反应快、还便宜,不少Java开发者都用它,本文整理了最常踩的8个坑,希望对... 目录引言一、坑 1:Token 过期未处理,鉴权异常引发服务中断问题本质典型错误代码解决方案:实现 Token

Java使用Spire.Barcode for Java实现条形码生成与识别

《Java使用Spire.BarcodeforJava实现条形码生成与识别》在现代商业和技术领域,条形码无处不在,本教程将引导您深入了解如何在您的Java项目中利用Spire.Barcodefor... 目录1. Spire.Barcode for Java 简介与环境配置2. 使用 Spire.Barco

SpringBoot集成iText快速生成PDF教程

《SpringBoot集成iText快速生成PDF教程》本文介绍了如何在SpringBoot项目中集成iText9.4.0生成PDF文档,包括新特性的介绍、环境准备、Service层实现、Contro... 目录SpringBoot集成iText 9.4.0生成PDF一、iText 9新特性与架构变革二、环

在C#中调用Windows防火墙界面的常见方式

《在C#中调用Windows防火墙界面的常见方式》在C#中调用Windows防火墙界面(基础设置或高级安全设置),可以使用进程启动(Process.Start)或Win32API来实现,所以本文给大家... 目录引言1. 直接启动防火墙界面(1) 打开基本防火墙设置(firewall.cpl)(2) 打开高

idea-java序列化serialversionUID自动生成方式

《idea-java序列化serialversionUID自动生成方式》Java的Serializable接口用于实现对象的序列化和反序列化,通过将对象转换为字节流来存储或传输,实现Serializa... 目录简介实现序列化serialVersionUID配置使用总结简介Java.io.Seripyth

python调用dubbo接口的实现步骤

《python调用dubbo接口的实现步骤》本文主要介绍了python调用dubbo接口的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编... 目录 ​​其他实现方式与注意事项​​ ​​高级技巧与集成​​用 python 提供 Dubbo 接口

Java中的随机数生成案例从范围字符串到动态区间应用

《Java中的随机数生成案例从范围字符串到动态区间应用》本文介绍了在Java中生成随机数的多种方法,并通过两个案例解析如何根据业务需求生成特定范围的随机数,本文通过两个实际案例详细介绍如何在java中... 目录Java中的随机数生成:从范围字符串到动态区间应用引言目录1. Java中的随机数生成基础基本随

C# FTP调用的实现示例

《C#FTP调用的实现示例》本文介绍了.NET平台实现FTP/SFTP操作的多种方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录1. 使用 .NET 自带 FtpWebRequest 实现 FTP 操作1.1 文件上传1.2

C#自动化生成PowerPoint(PPT)演示文稿

《C#自动化生成PowerPoint(PPT)演示文稿》在当今快节奏的商业环境中,演示文稿是信息传递和沟通的关键工具,下面我们就深入探讨如何利用C#和Spire.Presentationfor.NET... 目录环境准备与Spire.Presentation安装核心操作:添加与编辑幻灯片元素添加幻灯片文本操