智能指针

在 C++ 中,智能指针(smart pointers)是用于管理动态分配对象生命周期的类模板。它们旨在帮助开发者自动管理内存,避免常见的内存泄漏问题,并简化资源管理。C++ 标准库提供了三种主要类型的智能指针:std::unique_ptrstd::shared_ptrstd::weak_ptr。每种类型都有其特定的应用场景。

智能指针的作用

  1. 自动内存管理 :智能指针能够自动释放所指向的对象,从而避免了手动调用 delete 可能导致的内存泄漏。
  2. 所有权语义
    • std::unique_ptr 实现独占所有权(exclusive ownership),即一个对象只能由一个 std::unique_ptr 所有。
    • std::shared_ptr 支持共享所有权(shared ownership),允许多个 std::shared_ptr 共同拥有同一个对象。
    • std::weak_ptr 用于解决循环引用的问题,它提供了一种非拥有的引用方式来观察 std::shared_ptr 管理的对象。

如何使用智能指针

1. std::unique_ptr

  • 作用 :确保只有一个指针可以指向某个对象,当该 std::unique_ptr 超出作用域或被显式删除时,其所管理的对象会被自动销毁。
  • 使用示例
cpp 复制代码
#include <iostream>
#include <memory> // 包含智能指针相关的头文件

int main() {
    // 创建一个 unique_ptr 指向一个新的 int 对象
    std::unique_ptr<int> smartPtr = std::make_unique<int>(10);
    
    if (smartPtr) {
        std::cout << "Value: " << *smartPtr << std::endl;
    }

    // 不需要手动调用 delete,离开作用域时自动释放
    return 0;
}
  • 转移所有权
cpp 复制代码
auto anotherPtr = std::move(smartPtr); // 将所有权从 smartPtr 转移到 anotherPtr
// 此时 smartPtr 已经不再拥有任何对象,anotherPtr 拥有原始对象

2. std::shared_ptr

  • 作用 :允许多个 std::shared_ptr 同时指向同一个对象,并且只有当最后一个 std::shared_ptr 被销毁时,该对象才会被释放。
  • 使用示例
cpp 复制代码
#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> sp1 = std::make_shared<int>(20);
    
    {
        std::shared_ptr<int> sp2 = sp1; // 增加引用计数
        std::cout << "sp1 use count: " << sp1.use_count() << std::endl; // 输出 2
    } // sp2 超出作用域,引用计数减少

    std::cout << "sp1 use count after sp2 is destroyed: " << sp1.use_count() << std::endl; // 输出 1
    
    return 0;
}

3. std::weak_ptr

主要作用:

1. 解决循环引用(reference cycle)问题

当两个或多个 shared_ptr 相互引用时,会导致内存泄漏(因为引用计数永远不为0)。用 weak_ptr 打破这种循环引用是它最主要的用途。

cpp 复制代码
struct B;
struct A {
    std::shared_ptr<B> b_ptr;
};
struct B {
    std::weak_ptr<A> a_ptr;  // 用 weak_ptr 避免循环引用
};

2. 临时访问共享资源,不延长资源生命周期

有时你希望访问某个资源,但不希望因为你访问它就延长它的生命,这时候就用 weak_ptr

cpp 复制代码
std::shared_ptr<int> sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp; // 不增加引用计数

if (auto spt = wp.lock()) {  // lock() 返回 shared_ptr,如果资源已释放则返回空指针
    std::cout << *spt << '\n';
} else {
    std::cout << "资源已经被释放\n";
}

weak_ptrshared_ptr关系

weak_ptrshared_ptr 是 C++ 智能指针库中的两个紧密相关的类型,它们的关系可以从以下几个方面来理解:

weak_ptr 是对 shared_ptr 的一种"非拥有"引用

  • shared_ptr 管理资源的生命周期 ,并维护引用计数(use_count)
  • weak_ptr 观察由 shared_ptr 管理的资源,但不参与引用计数,也不会影响资源的释放时间。
cpp 复制代码
std::shared_ptr<int> sp = std::make_shared<int>(10);
std::weak_ptr<int> wp = sp;  // wp 不增加引用计数

2. weak_ptr 可以从 shared_ptr 构造而来,反之不能直接构造

  • ✅ 从 shared_ptr 创建 weak_ptr(不会增加引用计数):
cpp 复制代码
std::shared_ptr<MyClass> sp = std::make_shared<MyClass>();
std::weak_ptr<MyClass> wp = sp;
  • ❌ 不能直接从 weak_ptr 获得裸指针或引用,需要先调用 .lock() 获取 shared_ptr
cpp 复制代码
if (auto spt = wp.lock()) {
    // 使用 spt 访问对象
}

3. 共享相同的引用控制块(control block)

  • shared_ptr 和由它生成的 weak_ptr 都共享同一个控制块。
    • 控制块中包含:
      • use_count:当前有多少个 shared_ptr 拥有资源。
      • weak_count:当前有多少个 weak_ptr 引用控制块。
  • use_count == 0 时,资源被释放;
  • use_count == 0 && weak_count == 0 时,控制块本身也被销毁。

4. 典型应用场景:解决循环引用

在两个类相互引用时,如果都用 shared_ptr,就可能发生资源无法释放的问题(循环引用)。这时,应该让其中一方使用 weak_ptr

cpp 复制代码
struct Node {
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;  // 防止循环引用
};

总结一句话:

shared_ptr 是资源的"所有者",weak_ptr 是资源的"旁观者";weak_ptr 依赖 shared_ptr 存在,不能单独使用。


如果你希望一个对象共享资源 → 用 shared_ptr; 如果你希望访问但不拥有资源(避免循环引用或延长生命周期)→ 用 weak_ptr

使用 weak_ptr 的几个关键点:

特性 说明
weak_ptr<T> 不拥有资源,不增加引用计数
.lock() 尝试获取资源的 shared_ptr,可能为空
.expired() 判断资源是否已经被销毁
.use_count() 返回当前资源被多少个 shared_ptr 拥有

智能指针的生命周期

shared_ptrweak_ptrunique_ptr 这些智能指针对象本身在离开作用域时会立刻被析构。 智能指针对象本身(shared_ptrweak_ptrunique_ptr)在离开作用域时都会立刻析构;是否释放资源,取决于它是否拥有资源,以及资源的引用计数是否归零。


关键点总结:

智能指针类型 指针对象(自身)离开作用域是否立刻析构? 资源是否一定被释放?
unique_ptr ✅ 是,立刻析构 ✅ 是,立即释放资源
shared_ptr ✅ 是,立刻析构 ⚠️ 仅当引用计数为 0 时释放资源
weak_ptr ✅ 是,立刻析构 ❌ 否,不影响资源是否释放(不拥有资源)

🔹 unique_ptr

  • 拥有唯一资源 ,离开作用域时:
    • 智能指针对象本身被析构 ✅
    • 所管理的资源也立即 delete
cpp 复制代码
{
    std::unique_ptr<int> up = std::make_unique<int>(10);
} // up 被销毁,对象立即释放

🔹 shared_ptr

  • 拥有共享资源 ,离开作用域时:
    • 智能指针对象本身析构 ✅
    • 资源只有在所有 shared_ptr 都销毁(引用计数 = 0)时才被释放 ⚠️
cpp 复制代码
{
    std::shared_ptr<int> sp1 = std::make_shared<int>(20);
    {
        std::shared_ptr<int> sp2 = sp1; // use_count = 2
    } // sp2 析构,但资源还没释放
} // sp1 析构,use_count = 0,资源释放

🔹 weak_ptr

  • 不拥有资源 ,离开作用域时:
    • weak_ptr 对象立刻析构 ✅
    • 它观察的资源不会受影响 ❌
cpp 复制代码
{
    std::weak_ptr<int> wp;
    {
        std::shared_ptr<int> sp = std::make_shared<int>(30);
        wp = sp;
    } // sp 析构,资源释放
} // wp 析构,但没影响资源释放时机

下面是一个完整的 C++ 示例程序,清晰演示了 shared_ptrweak_ptrunique_ptr 在何时被销毁(对象析构)。

我们定义一个简单的类,打印构造与析构,以便观察指针何时释放资源:


✅ 示例代码(含注释):

cpp 复制代码
#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass(int val) : value(val) {
        std::cout << "MyClass(" << value << ") constructed.\n";
    }
    ~MyClass() {
        std::cout << "MyClass(" << value << ") destructed.\n";
    }

private:
    int value;
};

void demo_shared_ptr() {
    std::cout << "\n[shared_ptr demo]\n";
    std::shared_ptr<MyClass> sp1 = std::make_shared<MyClass>(1);
    {
        std::shared_ptr<MyClass> sp2 = sp1;
        std::cout << "shared_ptr use_count: " << sp1.use_count() << "\n";
    } // sp2 离开作用域,use_count 减 1
    std::cout << "shared_ptr use_count: " << sp1.use_count() << "\n";
} // sp1 离开作用域,对象析构

void demo_weak_ptr() {
    std::cout << "\n[weak_ptr demo]\n";
    std::weak_ptr<MyClass> wp;
    {
        std::shared_ptr<MyClass> sp = std::make_shared<MyClass>(2);
        wp = sp;
        std::cout << "Inside scope, expired? " << std::boolalpha << wp.expired() << "\n";
    } // sp 被销毁,资源被释放
    std::cout << "Outside scope, expired? " << std::boolalpha << wp.expired() << "\n";
    if (auto locked = wp.lock()) {
        std::cout << "locked shared_ptr is valid\n";
    } else {
        std::cout << "locked shared_ptr is null\n";
    }
}

void demo_unique_ptr() {
    std::cout << "\n[unique_ptr demo]\n";;

    std::unique_ptr<MyClass> up1 = std::make_unique<MyClass>(3);
    std::cout << "up1 owns the resource\n";

    // std::unique_ptr<MyClass> up2 = up1; // ❌ 错误:不能拷贝
    std::unique_ptr<MyClass> up2 = std::move(up1); // ✅ 移动所有权

    if (!up1) {
        std::cout << "up1 is now null after move\n";
    }
    if (up2) {
        std::cout << "up2 owns the resource after move\n";
    }

    // 用 reset() 释放当前对象
    up2.reset();  // 调用析构函数,释放 MyClass(3)
    std::cout << "up2.reset() called, resource released\n";

    // 用 reset(new T) 替换为新的对象
    up2.reset(new MyClass(4));
    std::cout << "up2 now owns a new object\n";

    // 离开作用域后,up2 自动释放新对象 MyClass(4)
}


int main() {
    demo_shared_ptr();
    demo_weak_ptr();
    demo_unique_ptr();
    std::cout << "\n[Program End]\n";
    return 0;
}

🧾 输出示例(说明资源释放时机):

text 复制代码
[shared_ptr demo]
MyClass(1) constructed.
shared_ptr use_count: 2
shared_ptr use_count: 1
MyClass(1) destructed.

[weak_ptr demo]
MyClass(2) constructed.
Inside scope, expired? false
MyClass(2) destructed.
Outside scope, expired? true
locked shared_ptr is null

[unique_ptr demo with move and reset]
MyClass(3) constructed.
up1 owns the resource
up1 is now null after move
up2 owns the resource after move
MyClass(3) destructed.
up2.reset() called, resource released
MyClass(4) constructed.
up2 now owns a new object
MyClass(4) destructed.

[Program End]

总结

  • std::unique_ptr:适用于单一所有权的情况,是最轻量级的选择,性能最优。
  • std::shared_ptr:适用于需要多个指针共享同一资源的场景,但要注意潜在的性能开销和循环引用问题。
  • std::weak_ptr :通常与 std::shared_ptr 结合使用,用来解决循环引用问题,或者仅作为临时引用而不影响对象生命周期。