破除循环引用:智能指针与友元函数的实践#

方法一:使用 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 持有 Childshared_ptr,拥有 Child 的所有权

  • Child 持有 Parentweak_ptr,仅观察而不拥有所有权

  • Parent 被销毁时,Childweak_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++ 资源管理的关键技能。三种方法各有优势:

  1. weak_ptr 方案:适用于复杂的对象网络,提供安全的共享访问

  2. boost::scoped_ptr 方案:简洁高效,适合明确的单向拥有关系

  3. 友元函数方案:适用于紧密协作但生命周期独立的对象

在实际开发中,应优先考虑使用智能指针(特别是独占指针)来管理资源,只在必要时使用友元函数。对于新项目,推荐使用 C++11 的 std::unique_ptrstd::shared_ptr/weak_ptr 组合;对于已有 Boost 的项目,boost::scoped_ptr 仍然是可靠的选择。

无论选择哪种方案,关键是清晰地表达对象间的所有权关系,这是编写安全、高效 C++ 代码的基础。