类中lambda捕获"自己"(通常是捕获this指针或类的shared_ptr实例),若满足"双向强引用"条件,则属于循环引用 ;若仅单向引用或弱引用,则不算。核心判断标准是:是否形成"类实例 → lambda → 类实例"的强引用闭环,导致双方生命周期无法正常结束。
下面分场景详细拆解,结合底层引用关系和实例说明:
一、先明确核心概念:什么是"lambda捕获自己"?
类中lambda"捕获自己",本质是捕获指向当前类实例的引用/指针,常见两种形式:
- 捕获
this指针 (非shared_ptr管理的类):lambda 持有当前类实例的裸指针(this)。 - 捕获
shared_ptr实例 (类继承std::enable_shared_from_this):lambda 持有当前类实例的强引用(shared_ptr<当前类>)。
关键前提:lambda 必须被类实例"长期持有" (如作为类的std::function成员变量),才可能形成循环引用。若lambda仅是局部变量(函数内临时创建,不被类持有),即使捕获this,也不会形成循环。
二、场景1:类实例持有lambda,lambda捕获this(裸指针)------ 不算严格意义的"循环引用",但有类似风险
底层引用关系
- 类实例(
A)持有 lambda:通过std::function成员变量(如std::function<void()> func_)存储lambda,形成"A→ lambda"的引用(std::function持有lambda的拷贝)。 - lambda 捕获
this:lambda 内部存储A*裸指针,形成"lambda → A"的指针引用(非强引用,不影响生命周期)。
为什么不算"循环引用"?
循环引用的核心是"强引用闭环 "(双方通过强引用互相持有,导致引用计数无法归零),而裸指针this不具备"延长生命周期"的特性:
- 类实例
A的生命周期由外部决定(如局部变量出作用域、shared_ptr计数归零)。 - lambda 持有
this裸指针,不会阻止A被销毁;但A销毁后,lambda 若继续调用this指向的成员,会导致野指针访问(未定义行为)。
示例代码(有风险,但非循环引用)
cpp
#include <iostream>
#include <functional>
class A {
public:
A() {
// lambda 捕获 this(裸指针),并被类实例持有(func_ 是成员变量)
func_ = [this]() {
std::cout << "lambda 调用类成员:" << this->num_ << std::endl;
};
}
~A() {
std::cout << "A 析构" << std::endl;
}
void callLambda() { func_(); }
private:
int num_ = 10;
std::function<void()> func_; // 类实例持有 lambda
};
int main() {
{
A a;
a.callLambda(); // 正常:lambda 通过 this 访问 a 的成员
} // a 出作用域,正常析构(lambda 随 a 销毁,无闭环)
return 0;
}
输出结果(无内存泄漏)
lambda 调用类成员:10
A 析构
风险点
若类实例被shared_ptr管理,且lambda被外部长期持有(如传入其他模块),A析构后lambda的this会变成野指针,调用时崩溃。但这不是"循环引用"(未形成强引用闭环),而是"悬垂指针"问题。
二、场景2:类实例(shared_ptr管理)持有lambda,lambda捕获shared_ptr------ 典型的循环引用
底层引用关系
- 类实例
A继承std::enable_shared_from_this<A>,外部通过std::shared_ptr<A>管理(形成强引用)。 - 类实例
A持有lambda:通过std::function成员变量存储lambda(A→ lambda)。 - lambda 捕获
shared_from_this():lambda 内部存储std::shared_ptr<A>(强引用),形成"lambda → A"的强引用。
形成强引用闭环
shared_ptr<A>(外部)→ A实例 → std::function → lambda → shared_ptr<A>(捕获的强引用),最终导致:
A实例的强引用计数永远无法归零(lambda 持有一个强引用,即使外部shared_ptr释放,计数仍为1)。A实例和lambda都无法被销毁,造成内存泄漏------ 这是严格意义上的"lambda相关的循环引用"。
示例代码(典型循环引用,内存泄漏)
cpp
#include <iostream>
#include <functional>
#include <memory>
class A : public std::enable_shared_from_this<A> { // 支持获取自身的 shared_ptr
public:
A() {
// lambda 捕获 shared_from_this()(强引用),并被类实例持有
func_ = [self = shared_from_this()]() {
std::cout << "lambda 调用类成员:" << self->num_ << std::endl;
};
}
~A() {
std::cout << "A 析构" << std::endl; // 永远不会执行!
}
void callLambda() { func_(); }
private:
int num_ = 10;
std::function<void()> func_; // 类实例持有 lambda
};
int main() {
{
std::shared_ptr<A> a = std::make_shared<A>();
a->callLambda(); // 正常调用,但形成循环引用
std::cout << "a 的强引用计数:" << a.use_count() << std::endl; // 输出 2!
} // 外部 shared_ptr<a> 析构,计数从 2 减为 1(lambda 仍持有强引用)
// A 实例未析构,内存泄漏
return 0;
}
输出结果(内存泄漏)
lambda 调用类成员:10
a 的强引用计数:2
底层原因
std::make_shared<A>()创建时,强引用计数为1。- lambda 捕获
shared_from_this(),强引用计数增至2。 - 外部
shared_ptr<a>出作用域,计数减为1(未归零)。 A实例和lambda互相持有强引用,永远无法销毁,形成循环引用。
三、场景3:类实例持有lambda,lambda捕获weak_ptr------ 不算循环引用(推荐方案)
若lambda捕获的是weak_ptr<当前类>(弱引用),则不会形成强引用闭环,避免循环引用。
底层引用关系
- 类实例
A→ lambda(std::function持有)。 - lambda →
weak_ptr<A>(弱引用,不增加强引用计数,不延长生命周期)。
示例代码(无循环引用)
cpp
#include <iostream>
#include <functional>
#include <memory>
class A : public std::enable_shared_from_this<A> {
public:
A() {
// 捕获 weak_ptr(弱引用),而非 shared_ptr
std::weak_ptr<A> weak_self = shared_from_this();
func_ = [weak_self]() {
// 访问前升级为 shared_ptr,检查实例是否存活
if (auto self = weak_self.lock()) {
std::cout << "lambda 调用类成员:" << self->num_ << std::endl;
} else {
std::cout << "类实例已销毁,lambda 无法调用" << std::endl;
}
};
}
~A() {
std::cout << "A 析构" << std::endl;
}
void callLambda() { func_(); }
private:
int num_ = 10;
std::function<void()> func_;
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
a->callLambda(); // 正常:weak_self.lock() 成功升级为 shared_ptr
std::cout << "a 的强引用计数:" << a.use_count() << std::endl; // 输出 1(lambda 持有弱引用,不计数)
{
auto a2 = a;
std::cout << "a2 持有后,计数:" << a.use_count() << std::endl; // 2
} // a2 析构,计数:1
a.reset(); // 释放外部强引用,计数:0 → A 析构
a->callLambda(); // 输出:类实例已销毁,lambda 无法调用(无野指针)
return 0;
}
输出结果(无内存泄漏)
lambda 调用类成员:10
a 的强引用计数:1
a2 持有后,计数:2
A 析构
类实例已销毁,lambda 无法调用
关键:弱引用不形成闭环
weak_ptr仅"观察"类实例,不增加强引用计数,因此:
- 外部
shared_ptr释放后,类实例的强引用计数可归零,正常析构。 - lambda 持有
weak_ptr,升级失败时不会访问已销毁的实例,避免野指针问题。
四、核心判断标准:是否形成"强引用闭环"
类中lambda捕获"自己"是否为循环引用,最终看是否满足:
类实例(强引用)→ lambda(std::function 持有)→ 类实例(强引用)
- 满足则是循环引用(场景2):导致内存泄漏,类实例和lambda无法销毁。
- 不满足则不算(场景1、3):
- 场景1(lambda持裸指针):无强引用闭环,但有野指针风险。
- 场景3(lambda持弱引用):无强引用闭环,无内存泄漏,安全。
五、总结与最佳实践
- 避免lambda捕获
shared_ptr形成强引用闭环 :若类由shared_ptr管理,lambda需捕获weak_ptr(通过shared_from_this()获取),访问前用lock()升级,检查实例存活状态。 - 非
shared_ptr管理的类,捕获this需注意生命周期 :确保lambda的生命周期不超过类实例,避免类销毁后lambda调用this。 - lambda不被类长期持有,则无风险 :若lambda是函数内临时使用(如作为算法参数),即使捕获
this,也不会形成循环,用完即销毁。 - 核心原则:循环引用的本质是"强引用闭环",与是否是lambda无关------lambda只是"中间载体",关键看它是否与类实例互相持有强引用。
简单记:类持lambda,lambda持类的强引用 → 循环引用;持弱引用/裸指针 → 不算,但裸指针有野指针风险。