Lambda中指针引用问题:定时器任务无法执行的解决方法
首先咱们来拆解下你遇到的两个核心问题:为什么直接用指针捕获会导致func执行时无输出,以及如何既保留修改原对象的能力(比如取消任务)又能让定时器正常工作。
一、原代码的问题根源
无限递归陷阱
你在at方法里把原taskinfo的func替换成了捕获taskinfo**的Lambda,然后把原对象的拷贝推入队列。当队列里的任务执行时,这个Lambda会调用(*task)->func——但此时原对象的func已经是这个Lambda本身了!这就会触发无限递归创建线程,最终导致程序无响应或者栈溢出,看起来像是func为空没有输出。任务队列与原对象脱节
你的priority_queue存储的是taskinfo的拷贝对象,而非指针。这意味着你调用cancel方法修改原对象的cancel标志时,队列里的拷贝对象的cancel不会同步变化,根本达不到取消任务的目的——这也是你需要修改原引用对象的核心诉求无法实现的原因。
另外你提到“拷贝本地对象后功能正常”,其实这是侥幸的未定义行为:Lambda捕获了本地taskinfo的引用,但本地对象在at方法返回后就销毁了,Lambda里的引用变成了悬空引用,只是测试环境下内存没被覆盖才暂时正常,实际运行中随时会崩溃。
二、正确的解决方案:用智能指针共享对象实例
要解决这两个问题,核心是让任务队列里的任务和原对象是同一个实例,同时避免递归调用。我们可以用std::shared_ptr来存储taskinfo,确保队列和外部共享同一个对象。
修改后的完整代码:
#include <functional> #include <chrono> #include <future> #include <queue> #include <thread> #include <memory> #include <sstream> #include <assert.h> #include <iostream> #include <ctime> #include <sys/time.h> #include <unistd.h> #include <limits.h> #define TIMER_NO_TASK_SLEEP_TIME 100 struct taskinfo { long int id; bool cancel; std::function<void()> func; std::chrono::system_clock::time_point time; std::chrono::system_clock::duration interval; taskinfo() {} // 为priority_queue定制的比较器(实现小顶堆,最早的任务优先执行) struct Compare { bool operator()(const std::shared_ptr<taskinfo>& a, const std::shared_ptr<taskinfo>& b) const { return a->time > b->time; } }; taskinfo(long int id, std::function<void()>&& f, const std::chrono::system_clock::time_point& t) : id(id), func(std::move(f)), time(t) { cancel = false; } }; class TimerTask { private: // 存储shared_ptr,确保队列与外部共享同一个taskinfo实例 std::priority_queue<std::shared_ptr<taskinfo>, std::vector<std::shared_ptr<taskinfo>>, taskinfo::Compare> tasks; std::unique_ptr<std::thread> thread; bool keepRunning; public: TimerTask() : keepRunning(true), thread(new std::thread([this]() { while(keepRunning) { auto now = std::chrono::system_clock::now(); while(!tasks.empty() && tasks.top()->time <= now) { auto task = tasks.top(); tasks.pop(); if(!task->cancel) { // 启动线程执行原func,避免阻塞定时器主线程 std::thread([func = std::move(task->func)]() { func(); }).detach(); } } if(tasks.empty()) { std::this_thread::sleep_for(std::chrono::milliseconds(TIMER_NO_TASK_SLEEP_TIME)); } else { std::this_thread::sleep_for(tasks.top()->time - std::chrono::system_clock::now()); } } })) {} ~TimerTask() { keepRunning = false; thread->join(); } // 直接传入shared_ptr,推入队列 void at(std::shared_ptr<taskinfo> task){ tasks.push(task); } // 修改原对象的cancel标志,队列里的实例会同步生效 void cancel(std::shared_ptr<taskinfo> task){ task->cancel = true; } }; void timeoutFunc(int id) { std::cout << "Request " << id << " Timeout.... Executed Timeout Function Time:" << std::chrono::system_clock::now().time_since_epoch().count() << std::endl; } int main(int argc, char* argv[]) { if(argc != 2) { std::cout << "\n Usage <Process> <Timeout>" << std::endl; return 0; } int timeout = atoi(argv[1]); TimerTask tT; std::vector<std::shared_ptr<taskinfo>> requests; requests.reserve(1000); for(int i=1; i <=5; i++ ) { auto t = std::make_shared<taskinfo>(i, [i]{ timeoutFunc(i); }, std::chrono::system_clock::now() + std::chrono::milliseconds(timeout)); tT.at(t); requests.push_back(t); std::cout << "Request " << i << " Registered.... Time:" << std::chrono::system_clock::now().time_since_epoch().count() << std::endl; } while(1) sleep(60); return 0; }
三、关键修改点说明
用
std::shared_ptr<taskinfo>存储任务
队列里不再存储对象拷贝,而是存储智能指针,确保外部和队列共享同一个taskinfo实例。这样调用cancel方法修改原对象的cancel标志时,队列里的任务也能看到这个变化,真正实现取消功能。移除原Lambda递归逻辑
定时器线程在执行任务时,直接提取原func并启动线程执行,避免了原代码中修改func导致的递归问题。同时用std::move转移func的所有权,避免不必要的拷贝。定制priority_queue的比较器
因为现在存储的是shared_ptr,需要重新定义比较逻辑,确保priority_queue按照任务的执行时间从小到大排序(小顶堆)。
这样修改后,你既可以通过cancel方法修改原对象的标志来取消任务,定时器也能正常执行任务输出结果了。
内容的提问来源于stack exchange,提问作者raghu

