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

相关文章

MybatisGenerator文件生成不出对应文件的问题

《MybatisGenerator文件生成不出对应文件的问题》本文介绍了使用MybatisGenerator生成文件时遇到的问题及解决方法,主要步骤包括检查目标表是否存在、是否能连接到数据库、配置生成... 目录MyBATisGenerator 文件生成不出对应文件先在项目结构里引入“targetProje

关于Maven生命周期相关命令演示

《关于Maven生命周期相关命令演示》Maven的生命周期分为Clean、Default和Site三个主要阶段,每个阶段包含多个关键步骤,如清理、编译、测试、打包等,通过执行相应的Maven命令,可以... 目录1. Maven 生命周期概述1.1 Clean Lifecycle1.2 Default Li

Python使用qrcode库实现生成二维码的操作指南

《Python使用qrcode库实现生成二维码的操作指南》二维码是一种广泛使用的二维条码,因其高效的数据存储能力和易于扫描的特点,广泛应用于支付、身份验证、营销推广等领域,Pythonqrcode库是... 目录一、安装 python qrcode 库二、基本使用方法1. 生成简单二维码2. 生成带 Log

Spring常见错误之Web嵌套对象校验失效解决办法

《Spring常见错误之Web嵌套对象校验失效解决办法》:本文主要介绍Spring常见错误之Web嵌套对象校验失效解决的相关资料,通过在Phone对象上添加@Valid注解,问题得以解决,需要的朋... 目录问题复现案例解析问题修正总结  问题复现当开发一个学籍管理系统时,我们会提供了一个 API 接口去

Java如何通过反射机制获取数据类对象的属性及方法

《Java如何通过反射机制获取数据类对象的属性及方法》文章介绍了如何使用Java反射机制获取类对象的所有属性及其对应的get、set方法,以及如何通过反射机制实现类对象的实例化,感兴趣的朋友跟随小编一... 目录一、通过反射机制获取类对象的所有属性以及相应的get、set方法1.遍历类对象的所有属性2.获取

Idea调用WebService的关键步骤和注意事项

《Idea调用WebService的关键步骤和注意事项》:本文主要介绍如何在Idea中调用WebService,包括理解WebService的基本概念、获取WSDL文件、阅读和理解WSDL文件、选... 目录前言一、理解WebService的基本概念二、获取WSDL文件三、阅读和理解WSDL文件四、选择对接

Python使用Pandas库将Excel数据叠加生成新DataFrame的操作指南

《Python使用Pandas库将Excel数据叠加生成新DataFrame的操作指南》在日常数据处理工作中,我们经常需要将不同Excel文档中的数据整合到一个新的DataFrame中,以便进行进一步... 目录一、准备工作二、读取Excel文件三、数据叠加四、处理重复数据(可选)五、保存新DataFram

SpringBoot生成和操作PDF的代码详解

《SpringBoot生成和操作PDF的代码详解》本文主要介绍了在SpringBoot项目下,通过代码和操作步骤,详细的介绍了如何操作PDF,希望可以帮助到准备通过JAVA操作PDF的你,项目框架用的... 目录本文简介PDF文件简介代码实现PDF操作基于PDF模板生成,并下载完全基于代码生成,并保存合并P

Java调用Python代码的几种方法小结

《Java调用Python代码的几种方法小结》Python语言有丰富的系统管理、数据处理、统计类软件包,因此从java应用中调用Python代码的需求很常见、实用,本文介绍几种方法从java调用Pyt... 目录引言Java core使用ProcessBuilder使用Java脚本引擎总结引言python

java中VO PO DTO POJO BO DO对象的应用场景及使用方式

《java中VOPODTOPOJOBODO对象的应用场景及使用方式》文章介绍了Java开发中常用的几种对象类型及其应用场景,包括VO、PO、DTO、POJO、BO和DO等,并通过示例说明了它... 目录Java中VO PO DTO POJO BO DO对象的应用VO (View Object) - 视图对象