C++11智能指针解析

        C++11 引入了 智能指针来解决手动管理动态内存的复杂性。它们能够自动管理堆内存,并在不再需要时自动释放,避免内存泄漏和悬空指针问题。C++11 提供了三种主要的智能指针类型:std::unique_ptrstd::shared_ptrstd::weak_ptr

1. std::unique_ptr:独占所有权

std::unique_ptr 是一种 独占所有权 的智能指针,它保证同一时间只能有一个智能指针拥有对象的所有权。这意味着对象只能被一个 unique_ptr 所管理,不能被多个指针共享。使用 std::unique_ptr 的场景通常是当一个对象的生命周期只需要在单个作用域内管理时。

特点
  • 独占所有权:不能复制,只能移动。
  • 生命周期:当 unique_ptr 离开其作用域时,会自动销毁并释放所管理的对象。
  • 轻量、高效:由于不涉及引用计数,unique_ptr 的效率通常比 shared_ptr 更高。
示例
#include <memory>
#include <iostream>

struct MyClass {
    MyClass() { std::cout << "MyClass constructed\n"; }
    ~MyClass() { std::cout << "MyClass destroyed\n"; }
};

int main() {
    std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>(); // 自动管理动态分配的 MyClass
    // std::unique_ptr<MyClass> ptr2 = ptr1; // 错误,不能复制 unique_ptr
    std::unique_ptr<MyClass> ptr2 = std::move(ptr1); // 正确,通过移动所有权
    // ptr1 现在为空
}
重要操作
  • 创建智能指针:使用 std::make_unique<T>() 动态分配内存并返回 std::unique_ptr<T>,避免手动使用 new
  • 移动所有权:使用 std::move 进行所有权转移,不能复制。

2. std::shared_ptr:共享所有权

std::shared_ptr 实现了 共享所有权,即可以有多个智能指针共同拥有同一个对象。对象的生命周期会被多个 shared_ptr 共享,只有当最后一个 shared_ptr 被销毁时,托管的对象才会被释放。

特点
  • 共享所有权:多个 shared_ptr 可以指向同一个对象。
  • 引用计数shared_ptr 维护一个引用计数器,当指向对象的所有 shared_ptr 被销毁时,引用计数器变为 0,对象会自动释放。
  • 适用场景:多个对象需要共享同一个资源时,例如在复杂的资源共享或对象关系管理中使用。
示例
#include <memory>
#include <iostream>

struct MyClass {
    MyClass() { std::cout << "MyClass constructed\n"; }
    ~MyClass() { std::cout << "MyClass destroyed\n"; }
};

int main() {
    std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
    {
        std::shared_ptr<MyClass> ptr2 = ptr1; // 引用计数增加
        std::cout << "Reference count: " << ptr1.use_count() << "\n"; // 输出 2
    }
    // 离开作用域,ptr2 被销毁,引用计数减少为 1
    std::cout << "Reference count: " << ptr1.use_count() << "\n"; // 输出 1
}
重要操作
  • 创建智能指针:使用 std::make_shared<T>() 创建共享指针,比手动分配内存效率更高。
  • 引用计数:使用 use_count() 查看当前引用计数。
注意事项
  • 循环引用问题:如果两个或多个 shared_ptr 对象形成循环引用(即 A 指向 B,B 又指向 A),那么即使引用计数变为 0,内存也不会被释放,导致内存泄漏。为了解决这个问题,引入了 std::weak_ptr

3. std::weak_ptr:弱引用

std::weak_ptr 是为了防止 std::shared_ptr 之间的 循环引用问题 而设计的。weak_ptr 并不影响引用计数,不拥有对象的所有权。它只是一个观察者,可以访问由 shared_ptr 管理的对象。如果 shared_ptr 管理的对象已经被释放,那么 weak_ptr 变为无效。

特点
  • 不增加引用计数weak_ptr 只是持有对象的引用,不会影响 shared_ptr 的引用计数。
  • 生命周期观察:使用 weak_ptr 观察对象的生命周期,可以通过 lock() 函数将 weak_ptr 转换为 shared_ptr,安全地访问对象。
示例
#include <memory>
#include <iostream>

struct MyClass {
    MyClass() { std::cout << "MyClass constructed\n"; }
    ~MyClass() { std::cout << "MyClass destroyed\n"; }
};

int main() {
    std::shared_ptr<MyClass> sptr = std::make_shared<MyClass>();
    std::weak_ptr<MyClass> wptr = sptr; // weak_ptr 引用 sptr,但不增加引用计数
    std::cout << "Reference count: " << sptr.use_count() << "\n"; // 输出 1

    if (auto shared = wptr.lock()) {
        std::cout << "Object is still alive\n";
    } else {
        std::cout << "Object has been destroyed\n";
    }

    sptr.reset(); // 释放 shared_ptr 管理的对象
    if (auto shared = wptr.lock()) {
        std::cout << "Object is still alive\n";
    } else {
        std::cout << "Object has been destroyed\n";
    }
}

shared_ptr 内部维护的关键成员变量:

1. 指向对象的原始指针(Managed Object Pointer)

  • 这是指向实际动态分配对象的原始指针。它是 shared_ptr 管理的对象,shared_ptr 使用这个指针来访问对象。
  • 例如:T* ptr,它指向动态分配的对象(例如通过 make_sharednew 分配的对象)。

2. 控制块(Control Block)

  • 控制块shared_ptr 背后维护的一个数据结构,包含了引用计数、弱引用计数以及其他额外的信息。控制块并不与所管理的对象共享地址,它是 shared_ptr 机制中的核心,包含多个关键数据:
    • 引用计数(Reference Counter)
      • 一个用于记录有多少个 shared_ptr 实例同时引用该对象的计数器,称为共享引用计数。当创建一个新的 shared_ptr 实例指向相同对象时,该计数器递增;当 shared_ptr 被销毁或重置时,该计数器递减。
      • 当引用计数降为 0 时,托管的对象会被销毁。
    • 弱引用计数(Weak Reference Counter)
      • 另一个计数器,记录有多少个 weak_ptr 实例引用该对象。weak_ptr 不会影响共享引用计数,但会影响控制块的生命周期。
      • 当共享引用计数和弱引用计数都降为 0 时,控制块本身也会被销毁。
    • 自定义删除器(Custom Deleter)
      • shared_ptr 可以绑定一个自定义删除器,用于控制对象的销毁方式。如果你不指定删除器,shared_ptr 会使用默认的 delete 运算符释放对象。
      • 删除器保存在控制块中,当最后一个 shared_ptr 离开作用域时,它会调用这个删除器来销毁对象。

3. 弱引用管理(Weak Pointer Management)

  • shared_ptrweak_ptr 的结合使用涉及到对控制块中弱引用计数的维护。weak_ptr 不会增加对对象的共享引用计数,但会使控制块的弱引用计数增加。这样,即使没有任何 shared_ptr 实例指向对象,控制块依然存在,直到所有 weak_ptr 也被销毁。

具体控制块结构的解释

可以将控制块理解为包含以下信息的结构体(这是一个概念上的简化):

struct ControlBlock {
    int shared_count;   // 共享引用计数
    int weak_count;     // 弱引用计数
    void(*deleter)(T*); // 自定义删除器函数指针
};

每次创建 shared_ptr 时,shared_count 会增加,而 weak_count 仅在创建 weak_ptr 时增加。

控制块与对象的关系

  • 独立于对象的存储:控制块是一个独立的数据结构,和对象的内存分配是分开的。当使用 make_shared 时,对象和控制块可能会被分配到同一块内存区域中,但它们的作用是不同的。
  • 生命周期管理:当 shared_count 变为 0 时,shared_ptr 管理的对象会被销毁;当 weak_count 也为 0 时,控制块才会被销毁。

shared_ptr 循环引用的例子

#include <iostream>
#include <memory>

struct B;

struct A {
    std::shared_ptr<B> ptrB;  // A持有B的shared_ptr
    ~A() { std::cout << "A destroyed\n"; }
};

struct B {
    std::shared_ptr<A> ptrA;  // B持有A的shared_ptr
    ~B() { std::cout << "B destroyed\n"; }
};

int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();

    a->ptrB = b;  // A拥有B
    b->ptrA = a;  // B拥有A

    // 循环引用导致A和B永远不会被销毁
    return 0;
}

在这个例子中:

  • A 持有一个指向 Bshared_ptr,而 B 也持有一个指向 Ashared_ptr
  • 由于 shared_ptr 会增加引用计数,ab 的引用计数永远无法减为 0,导致它们的析构函数不会被调用,从而造成内存泄漏。

解决循环引用:使用 std::weak_ptr

要解决循环引用问题,可以将其中一个 shared_ptr 替换为 std::weak_ptrweak_ptr 不会影响引用计数,因此可以打破循环引用。

解决后的代码

#include <iostream>
#include <memory>

struct B;

struct A {
    std::shared_ptr<B> ptrB;  // A持有B的shared_ptr
    ~A() { std::cout << "A destroyed\n"; }
};

struct B {
    std::weak_ptr<A> ptrA;    // B持有A的weak_ptr,打破循环引用
    ~B() { std::cout << "B destroyed\n"; }
};

int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();

    a->ptrB = b;  // A拥有B
    b->ptrA = a;  // B弱引用A,不增加引用计数

    // 正常销毁A和B,输出"A destroyed"和"B destroyed"
    return 0;
}

关键点

  • BA 的引用改为 std::weak_ptrweak_ptr 不增加引用计数,不会阻止对象的销毁。
  • 在需要使用 weak_ptr 指向的对象时,可以调用 lock() 方法将其转换为 shared_ptr,如果对象已被销毁,则返回 nullptr

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值