在C++11引入的Lambda表达式语法中,我们可以使用[=]
、[&]
等符号来捕获当前作用域中的变量,而同时也可以使用[this]
来捕获当前对象的this
指针。然而,在多线程环境下,如果Lambda表达式捕获了this
指针但没有正确处理,可能会导致对象在析构后依然被Lambda表达式所引用,从而产生严重的未定义行为,比如内存泄漏、指针悬挂等。
为了避免这种问题,我们需要理解Lambda表达式捕获this
的工作原理,并在代码中正确使用。
下面是一个EasyX库绘图程序的示例,展示了如何使用Lambda表达式来处理多线程绘图任务。其中startRenderTask()
函数将会创建一个线程,该线程将会循环执行提交的绘图任务,直到shouldStopRenderTask()
函数返回true
为止。renderTask()
函数将会产生一个Graphics
对象,然后调用renderCallback_
回调函数进行绘图操作。
class Graphics {
public:
Graphics(int width, int height) : width_(width), height_(height) {
// 省略其他构造函数内容
}
~Graphics() {
// 省略其他析构函数内容
}
void DrawLine(int x1, int y1, int x2, int y2) {
// 省略绘图代码
}
private:
// 省略其他成员变量与函数
int width_, height_;
};
class RenderTask {
public:
RenderTask(int width, int height) {
graphics_ = new Graphics(width, height);
}
~RenderTask() {
delete graphics_;
}
void run() {
// 省略绘图代码
graphics_->DrawLine(0, 0, 100, 100);
}
Graphics* getGraphics() const {
return graphics_;
}
private:
Graphics* graphics_;
};
class GraphicsRenderer {
public:
GraphicsRenderer(int width, int height) : should_stop_(false), task_(width, height) {
thread_ = std::thread(&GraphicsRenderer::startRenderTask, this);
}
~GraphicsRenderer() {
should_stop_ = true;
thread_.join();
}
void setRenderCallback(std::function<void(Graphics*)> callback) {
renderCallback_ = callback;
}
void submitRenderTask(std::unique_ptr<RenderTask> task) {
std::unique_lock<std::mutex> lock(task_mutex_);
task_queue_.push(std::move(task));
task_condition_.notify_one();
}
bool shouldStopRenderTask() const {
return should_stop_;
}
private:
void startRenderTask() {
std::unique_ptr<RenderTask> task = nullptr;
while (!shouldStopRenderTask()) {
{
std::unique_lock<std::mutex> lock(task_mutex_);
while (task_queue_.empty()) {
task_condition_.wait(lock);
}
task = std::move(task_queue_.front());
task_queue_.pop();
}
if (task) {
auto graphics = task->getGraphics();
renderCallback_(graphics);
delete task;
task = nullptr;
}
}
}
std::thread thread_;
bool should_stop_;
std::function<void(Graphics*)> renderCallback_;
std::queue<std::unique_ptr<RenderTask>> task_queue_;
std::mutex task_mutex_;
std::condition_variable task_condition_;
};
在使用Lambda表达式作为回调函数时,我们需要注意几个问题:
问题 1:如何正确捕获this指针?
在回调函数中使用Lambda表达式时,我们通常需要捕获当前类对象的this
指针,从而访问类的成员变量和函数。然而,如果我们使用错误的捕获方式,会出现对象在析构后仍然被Lambda表达式所引用的问题。
例如,在GraphicsRenderer::setRenderCallback()
函数中,我们可能会将Lambda表达式回调函数的捕获方式设置为[=]
或[&]
,从而捕获当前作用域下的所有变量。但是,这样会导致Lambda表达式捕获当前作用域下的所有变量,从而可能包括当前对象的this
指针。当GraphicsRenderer
对象在析构时,由于Lambda表达式仍然引用了this
指针,从而可能导致未定义行为。
为了避免该问题,我们应该使用[this]
作为捕获方式,这样可以保证只捕获当前对象的this
指针,从而避免对象在析构后仍被Lambda表达式所引用的问题。
例如,修改GraphicsRenderer::setRenderCallback()
函数如下:
void setRenderCallback(std::function<void(Graphics*)> callback) {
renderCallback_ = [this, callback](Graphics* graphics) {
callback(graphics);
};
}
注意,在Lambda表达式中,我们需要将外部传入的函数指针作为参数传递给内部的Lambda表达式,从而避免捕获错误的变量。
问题 2:如何避免对象在析构后,Lambda仍然访问对象的成员变量或函数?
当使用Lambda表达式作为回调函数时,我们需要明确回调函数的生命周期,以避免在对象销毁后,Lambda表达式仍然访问对象成员变量或函数的问题。
例如,在上面的示例中,回调函数renderCallback_
可能在多个线程中被调用,如果在回调函数中使用对象的成员变量或函数,很容易出现线程竞争问题,导致未定义行为。
为了避免该问题,我们可以采用以下两种方式之一:
- 确保所有回调函数的生命周期必须在对象的生命周期内。在析构函数中等待所有回调函数执行完毕后,再释放对象。
- 在回调函数中不要使用对象的成员变量或函数,而是在回调函数中构造一个新的
std::shared_ptr
指针,以指向需要访问的成员变量或函数,并捕获该指针。这样可以确保所有操作都在回调函数内完成,避免了竞争问题。
例如,在上面的示例中,我们可以使用第二种方法,修改GraphicsRenderer
的相关部分代码如下:
class GraphicsRenderer {
public:
GraphicsRenderer(int width, int height) : should_stop_(false), task_(width, height), graphics_(nullptr) {
thread_ = std::thread(&GraphicsRenderer::startRenderTask, this);
}
~GraphicsRenderer() {
should_stop_ = true;
thread_.join();
graphics_.reset();
}
void setRenderCallback(std::function<void(std::shared_ptr<Graphics>)> callback) {
renderCallback_ = [this, callback](Graphics* graphics) {
std::shared_ptr<Graphics> shared_graphics(graphics, [this](Graphics* graphics) {
delete graphics;
});
callback(shared_graphics);
};
}
void submitRenderTask(std::unique_ptr<RenderTask> task) {
std::unique_lock<std::mutex> lock(task_mutex_);
task_queue_.push(std::move(task));
task_condition_.notify_one();
}
bool shouldStopRenderTask() const {
return should_stop_;
}
private:
void startRenderTask() {
std::unique_ptr<RenderTask> task = nullptr;
while (!shouldStopRenderTask()) {
{
std::unique_lock<std::mutex> lock(task_mutex_);
while (task_queue_.empty()) {
task_condition_.wait(lock);
}
task = std::move(task_queue_.front());
task_queue_.pop();
}
if (task) {
graphics_.reset(task->getGraphics());
renderCallback_(graphics_.get());
task.reset(nullptr);
}
}
}
std::thread thread_;
bool should_stop_;
std::function<void(std::shared_ptr<Graphics>)> renderCallback_;
std::queue<std::unique_ptr<RenderTask>> task_queue_;
std::mutex task_mutex_;
std::condition_variable task_condition_;
std::shared_ptr<Graphics> graphics_;
};
修改后的代码中,我们将Lambda表达式改为捕获std::shared_ptr<Graphics>
指针,并在回调函数中使用该指针来访问Graphics
对象的成员变量和函数。该方式可以确保所有操作都在回调函数内完成,避免了竞争问题。同时,我们在std::shared_ptr
的构造函数中指定了一个lambda表达式作为deleter,用于释放Graphics对象内存。需要注意的是,在shtGraphics
变量被销毁时,其内部持有的指向Graphics对象的内存也会被自动释放。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:c++ lambda捕获this 导致多线程下类释放后还在使用的错误问题 - Python技术站