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

相关文章

C++对象布局及多态实现探索之内存布局(整理的很多链接)

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等 文章链接:http://dev.yesky.com/254/2191254.shtml      论C/C++函数间动态内存的传递 (2005-07-30)   当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道。 文章链接:http://dev.yesky

ABAP怎么把传入的参数刷新到内表里面呢?

1.在执行相关的功能操作之前,优先执行这一段代码,把输入的数据更新入内表里面 DATA: lo_guid TYPE REF TO cl_gui_alv_grid.CALL FUNCTION 'GET_GLOBALS_FROM_SLVC_FULLSCR'IMPORTINGe_grid = lo_guid.CALL METHOD lo_guid->check_changed_data.CALL M

android 带与不带logo的二维码生成

该代码基于ZXing项目,这个网上能下载得到。 定义的控件以及属性: public static final int SCAN_CODE = 1;private ImageView iv;private EditText et;private Button qr_btn,add_logo;private Bitmap logo,bitmap,bmp; //logo图标private st

C++面试八股文:std::deque用过吗?

100编程书屋_孔夫子旧书网 某日二师兄参加XXX科技公司的C++工程师开发岗位第26面: 面试官:deque用过吗? 二师兄:说实话,很少用,基本没用过。 面试官:为什么? 二师兄:因为使用它的场景很少,大部分需要性能、且需要自动扩容的时候使用vector,需要随机插入和删除的时候可以使用list。 面试官:那你知道STL中的stack是如何实现的吗? 二师兄:默认情况下,stack使

20170723 做的事 ecdsa的签名验证时间短于bls signature

1 今天在虚拟机 /home/smile/Desktop/20170610/Test//time_ecdsa 文件夹下,找到ecdsa的验证时间是 989.060606μs μs 先 make ,然后run。 再取BLS的签名生成时间: ./run  2  gnuplot 画图,画对比的时间 gnuplot 画图参考教程 http://blog.sciencen

API-环境对象

学习目标: 掌握环境对象 学习内容: 环境对象作用 环境对象: 指的是函数内部特殊的变量this,它代表着当前函数运行时所处的环境。 作用: 弄清楚this的指向,可以让我们代码更简洁。 函数的调用方式不同,this指代的对象也不同。【谁调用,this就是谁】是判断this指向的粗略规则。直接调用函数,其实相当于是window.函数,所以this指代window。

python 在pycharm下能导入外面的模块,到terminal下就不能导入

项目结构如下,在ic2ctw.py 中导入util,在pycharm下不报错,但是到terminal下运行报错  File "deal_data/ic2ctw.py", line 3, in <module>     import util 解决方案: 暂时方案:在终端下:export PYTHONPATH=/Users/fujingling/PycharmProjects/PSENe

FastAdmin/bootstrapTable 表格中生成的按钮设置成文字

公司有个系统后台框架用的是FastAdmin,后台表格的操作栏按钮只有图标,想要设置成文字。 查资料后发现其实很简单,主需要新增“text”属性即可,如下 buttons: [{name: 'acceptcompany',title: '复核企业',text:'复核企业',classname: 'btn btn-xs btn-primary btn-dialog',icon: 'fa fa-pe

Python分解多重列表对象,isinstance实现

“”“待打印的字符串列表:['ft','bt',['ad',['bm','dz','rc'],'mzd']]分析可知,该列表内既有字符对象,又有列表对象(Python允许列表对象不一致)现将所有字符依次打印并组成新的列表”“”a=['ft','bt',['ad',['bm','dz','rc'],'mzd']]x=[]def func(y):for i in y:if isinst

相同的生命周期

1.保证相同的生命周期 bool GameOverLayer::init()   {       if (CCLayerColor::initWithColor(ccc4(0, 255, 0, 255))){              _label = CCLabelTTF::create("word", "Artial", 40);              CCSize size = CC