C++相互依赖对象回调持有关系及生命周期安全问题咨询
class D { public: void HelloWorld(){cout << "Hello" << endl;} }; class B { public: void SetCallBack(std::function<void()> callback) { callback_ = callback; } private: std::function<void()> callback_; }; class A { public: A(B *b) : b_(b) {} std::function<void()> Helper(){ return [](){d_->HelloWorld(); }; } private: B *b_; std::unique_ptr<D> d_; }; class C { public: C(std::unique_ptr<B> b, std::unique_ptr<A> a) : b_(std::move(b)), a_(std::move(a)) { b_->SetCallBack(a_->Helper()); } private: std::unique_ptr<B> b_; std::unique_ptr<A> a_; };
预说明:基础编译错误
你给出的代码本身无法通过编译:A::Helper里返回的lambda是空捕获,既没有捕获当前A对象的this指针,也没有单独捕获成员变量d_,直接访问非静态成员d_属于语法错误,编译器会直接报错。
下文默认你是想把lambda写为捕获this的版本(即return [this](){d_->HelloWorld(); };),再对应回答两个问题。
问题1:B持有回调是否等于持有A的指针?
- 修正lambda捕获逻辑的前提下:是的。
std::function会存储lambda的所有捕获值,这里捕获的this就是A对象的裸指针,所以B持有的回调副本会间接持有A对象的指针。 - 如果lambda是其他捕获逻辑(比如单独捕获
d_的智能指针对象),那B持有的内容会随捕获逻辑变化,不一定持有A的指针。 - 按你原本写错的空捕获lambda:代码无法编译,不存在该问题。
问题2:A先析构后B调用回调会发生什么?代码有没有bug?
首先明确C的成员析构规则:C++中类成员的构造顺序和声明顺序一致,析构顺序和构造顺序相反。C类的成员声明顺序是先b_后a_,所以C对象析构时,会先析构a_(A对象先销毁),再析构b_(B对象后销毁)。
在lambda捕获逻辑修正的前提下:
- A对象被销毁后,回调里捕获的
this指针就变成野指针,同时A持有的d_也会被同步销毁。此时如果B调用callback_,就会触发野指针访问,属于未定义行为,大概率直接崩溃。 - 额外还有一个隐藏问题:代码里A的
d_是unique_ptr但没有初始化,就算A没被析构,调用回调访问d_->HelloWorld也会因为空指针解引用崩溃。
这段代码存在明确bug:
- 基础语法错误:lambda捕获逻辑错误导致编译不通过
- 空指针问题:A的
d_未初始化,即使修复捕获逻辑,正常调用回调也会崩溃 - 悬垂指针风险:C的析构顺序是先销毁A后销毁B,如果B在析构阶段或者其他时机在A销毁后调用回调,就会触发野指针访问
- 设计层面循环依赖:A依赖B的裸指针,B又持有A生成的回调间接依赖A,生命周期没有明确约束,极易出现内存安全问题
内容的提问来源于stack exchange,提问作者sfdcnoob
相关产品推荐
相关产品推荐

