破除循环引用:智能指针与友元函数的实践#
方法一:使用 weak_ptr 破除智能指针循环引用#
在对象间存在父子关系时,使用智能指针可能导致循环引用。通过让 parent 持有 child 的 shared_ptr,而 child 持有 parent 的 weak_ptr,可以安全地破除循环引用,避免内存泄漏。
#include <iostream>
#include <memory>
class Child; // 前向声明
class Parent {
private:
std::shared_ptr<Child> child; // parent 持有 child 的 shared_ptr
public:
Parent() { std::cout << "Parent created\n"; }
~Parent() { std::cout << "Parent destroyed\n"; }
void setChild(const std::shared_ptr<Child>& c) { child = c; }
};
class Child {
private:
std::weak_ptr<Parent> parent; // child 持有 parent 的 weak_ptr,避免循环引用
public:
Child(const std::shared_ptr<Parent>& p) : parent(p) { std::cout << "Child created\n"; }
~Child() { std::cout << "Child destroyed\n"; }
void useParent() {
if (auto sp = parent.lock()) { // 安全地获取 parent
std::cout << "Using parent\n";
} else {
std::cout << "Parent already destroyed\n";
}
}
};
int main() {
// 创建 parent 和 child
auto parent = std::make_shared<Parent>();
auto child = std::make_shared<Child>(parent);
// 建立双向关系
parent->setChild(child);
// 使用 child 访问 parent
child->useParent();
// 当 main 函数结束时,parent 和 child 都能正确销毁
// 不会发生内存泄漏
return 0;
}
关键点说明:
Parent持有Child的shared_ptr,拥有Child的所有权Child持有Parent的weak_ptr,仅观察而不拥有所有权当
Parent被销毁时,Child的weak_ptr自动失效使用
weak_ptr::lock()安全地访问Parent对象
方法二:使用 boost::scoped_ptr 实现单向所有权#
boost::scoped_ptr 提供严格的独占所有权语义,不能复制或转移所有权,非常适合表达明确的单向拥有关系。这可以天然地避免循环引用问题。
#include <iostream>
#include <boost/scoped_ptr.hpp>
class Component; // 前向声明
class Owner {
private:
boost::scoped_ptr<Component> component; // 独占拥有 Component
public:
Owner();
~Owner() { std::cout << "Owner destroyed\n"; }
void useComponent();
};
class Component {
private:
Owner* owner; // 只持有原始指针,不拥有所有权
public:
Component(Owner* own) : owner(own) { std::cout << "Component created\n"; }
~Component() { std::cout << "Component destroyed\n"; }
void doSomething() {
if (owner) {
std::cout << "Component accessing owner\n";
}
}
};
Owner::Owner() : component(new Component(this)) {
std::cout << "Owner created\n";
}
void Owner::useComponent() {
if (component) {
component->doSomething();
}
}
int main() {
{
Owner owner; // 创建 Owner,自动创建 Component
owner.useComponent();
// 当 owner 离开作用域时:
// 1. owner 被销毁
// 2. component 自动被销毁
// 3. 没有循环引用问题
}
std::cout << "Both Owner and Component properly destroyed\n";
return 0;
}
scoped_ptr 特点:
独占所有权:不能复制或赋值,天然避免所有权共享
自动管理:离开作用域时自动释放资源
明确语义:清晰地表达”拥有”关系
性能优越:开销几乎与原始指针相同
方法三:使用友元函数破除循环引用#
当类之间存在紧密协作但不需要共享所有权时,可以使用友元函数来破除循环依赖,同时保持封装性。
#include <iostream>
class Sniper; // 前向声明
class Supplier {
private:
int storage;
public:
Supplier(int storage = 1000) : storage(storage) {}
// 声明友元关系
bool provide(Sniper& sniper);
int getStorage() const { return storage; }
};
class Sniper {
private:
int bullets;
// 声明 Supplier::provide 为友元函数
friend bool Supplier::provide(Sniper& sniper);
public:
Sniper(int bullets = 0) : bullets(bullets) {}
int getBullets() const { return bullets; }
};
// 友元函数实现 - 可以访问 Sniper 的私有成员
bool Supplier::provide(Sniper& sniper) {
if (sniper.bullets < 20) { // 直接访问私有成员
if (storage > 100) {
sniper.bullets += 100;
storage -= 100;
} else if (storage > 0) {
sniper.bullets += storage;
storage = 0;
} else {
return false;
}
}
std::cout << "Sniper has " << sniper.bullets << " bullets, Supplier has " << storage
<< " storage left.\n";
return true;
}
int main() {
Sniper sniper(5); // 初始只有 5 发子弹
Supplier supplier(250); // 初始有 250 库存
std::cout << "Initial state:\n";
std::cout << "Sniper bullets: " << sniper.getBullets() << "\n";
std::cout << "Supplier storage: " << supplier.getStorage() << "\n\n";
// 供应商为狙击手补充弹药
supplier.provide(sniper);
return 0;
}
三种方法的对比与选择#
特性 |
weak_ptr 方法 |
boost::scoped_ptr 方法 |
友元函数方法 |
|---|---|---|---|
所有权语义 |
共享所有权 + 弱引用 |
严格独占所有权 |
无所有权关系 |
内存安全 |
自动管理,防止泄漏 |
自动管理,无泄漏风险 |
需手动管理生命周期 |
灵活性 |
高,支持共享和弱引用 |
低,所有权不可转移 |
中等,仅提供访问权限 |
性能开销 |
中等(引用计数) |
极低(近似原始指针) |
无额外开销 |
适用场景 |
需要共享访问的对象图 |
明确的独占拥有关系 |
紧密协作的独立对象 |
循环引用风险 |
可安全破除 |
天然避免(单向拥有) |
无智能指针,无循环引用 |
实际应用建议#
1. 根据所有权关系选择#
明确父子关系 → 使用
boost::scoped_ptr(父拥有子)或weak_ptr(双向弱引用)资源共享 → 使用
shared_ptr+weak_ptr独立协作 → 使用友元函数 + 原始指针
2. 现代 C++ 替代方案#
在 C++11 及以上版本,可以使用 std::unique_ptr 替代 boost::scoped_ptr:
#include <memory>
class ModernOwner {
private:
std::unique_ptr<Component> component; // C++11 独占指针
public:
ModernOwner() : component(std::make_unique<Component>(this)) {}
// unique_ptr 提供移动语义,比 scoped_ptr 更灵活
};
3. 混合模式示例#
#include <memory>
#include <boost/scoped_ptr.hpp>
class ComplexSystem {
private:
// 独占核心组件
boost::scoped_ptr<CoreComponent> core;
// 共享可替换模块
std::shared_ptr<PluginModule> plugin;
// 观察者使用弱引用
std::vector<std::weak_ptr<Observer>> observers;
public:
ComplexSystem() : core(new CoreComponent()) {}
void setPlugin(std::shared_ptr<PluginModule> p) { plugin = p; }
// 友元函数用于深度优化
friend void optimizeSystem(ComplexSystem& sys);
};
总结#
破除循环引用是 C++ 资源管理的关键技能。三种方法各有优势:
weak_ptr方案:适用于复杂的对象网络,提供安全的共享访问boost::scoped_ptr方案:简洁高效,适合明确的单向拥有关系友元函数方案:适用于紧密协作但生命周期独立的对象
在实际开发中,应优先考虑使用智能指针(特别是独占指针)来管理资源,只在必要时使用友元函数。对于新项目,推荐使用 C++11 的 std::unique_ptr 和 std::shared_ptr/weak_ptr 组合;对于已有 Boost 的项目,boost::scoped_ptr 仍然是可靠的选择。
无论选择哪种方案,关键是清晰地表达对象间的所有权关系,这是编写安全、高效 C++ 代码的基础。