C++ 智能指针 unique_ptr 和 shared_ptr

FreeGuideOnline 最新 2026-07-08

cpp #include #include

struct Widget { Widget(int id) : id_(id) { std::cout << "Widget " << id_ << " created\n"; } ~Widget() { std::cout << "Widget " << id_ << " destroyed\n"; } int id_; };

int main() { // 使用 make_unique(推荐) auto p1 = std::make_unique(10);

// 直接从原始指针构造(需要显式 delete,不推荐,易出错)
std::unique_ptr<Widget> p2(new Widget(20));

// p1 和 p2 在离开作用域时自动销毁

}


#### 移动语义,不能复制
`unique_ptr` **禁止拷贝**,只能移动。移动后源指针变为 `nullptr`,所有权被转移:

```cpp
auto p1 = std::make_unique<int>(5);
// auto p2 = p1;              // 编译错误,删除拷贝构造
auto p2 = std::move(p1);      // 合法,p1 变为 nullptr,p2 拥有资源

if (p1) {
    std::cout << *p1;          // 不会执行,因为 p1 为空
}
std::cout << *p2;              // 输出 5

访问原始指针

  • get() 返回原始指针,但不会释放所有权。
  • 像普通指针一样使用 *-> 解引用。
  • 检查是否为空:if (p)p != nullptr
auto uptr = std::make_unique<std::string>("hello");
if (uptr) {
    std::cout << uptr->size();   // 使用 ->
    std::cout << (*uptr)[0];     // 使用 *
}
// 不要对 get() 返回的指针执行 delete!

释放与重置

  • reset() 销毁当前对象(若持有),并使 unique_ptr 变为空。
  • reset(原始指针) 接管新指针,旧对象被销毁。
  • release() 放弃所有权并返回原始指针,但不销毁对象,需自行管理。
auto ptr = std::make_unique<int>(42);
ptr.reset();            // 释放对象,ptr 为空
ptr.reset(new int(7));  // 接管新的 int(7)

int* raw = ptr.release();  // 放弃所有权,raw 指向 int(7),ptr 为空
delete raw;                // 必须手动释放!

自定义删除器

可指定额外的清理逻辑,例如管理 FILE* 或自定义资源:

// 使用 lambda 作为删除器关闭文件
auto file_deleter = [](FILE* f) { if (f) fclose(f); };
std::unique_ptr<FILE, decltype(file_deleter)> 
    filePtr(fopen("test.txt", "r"), file_deleter);

数组形式

std::unique_ptr 对数组有特化版本 std::unique_ptr<T[]>,会自动调用 delete[]

auto arr = std::make_unique<int[]>(5);  // 分配 int[5]
arr[0] = 1;  // 支持下标运算

make_unique 自动推导数组大小,需显式指定长度。

适用场景

  • 独占某个资源,如文件句柄、数据库连接。
  • 工厂函数返回对象,所有权明确转移给调用者。
  • 在容器中管理多态对象(配合移动语义)。

shared_ptr:共享所有权

什么是 shared_ptr

std::shared_ptr 使用引用计数实现共享所有权。多个 shared_ptr 可以指向同一个对象,当最后一个 shared_ptr 被销毁或重置时,对象才会被释放。

创建与引用计数

推荐使用 std::make_shared<T>,它一次性分配对象和控制块,效率更高且异常安全:

#include <memory>

auto sp1 = std::make_shared<int>(10);
auto sp2 = sp1;          // sp1 和 sp2 指向同一个 int,引用计数变为2

sp2.reset();             // sp2 不再指向对象,引用计数减为1
// 当 sp1 销毁时,引用计数变 0,释放 int

从原始指针构造也可以,但可能导致问题(见后文)。

内部机制:控制块

每个 shared_ptr 管理一个控制块,记录:

  • 引用计数(强引用计数)
  • 弱引用计数(给 weak_ptr 使用)
  • 删除器、分配器等信息

使用 make_shared 时,对象内存与控制块在同一内存块中分配,减少动态分配次数。

自定义删除器

unique_ptr 类似,但删除器存储在控制块中,不影响 shared_ptr 类型大小:

auto deleter = [](int* p) { delete p; };
std::shared_ptr<int> sp(new int, deleter);

循环引用与 weak_ptr

shared_ptr 的循环引用是著名陷阱:两个对象互相持有对方的 shared_ptr,引用计数永远不为0,导致内存泄漏。

struct B;
struct A {
    std::shared_ptr<B> b_ptr;
    ~A() { std::cout << "A destroyed\n"; }
};
struct B {
    std::shared_ptr<A> a_ptr;
    ~B() { std::cout << "B destroyed\n"; }
};

{
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();
    a->b_ptr = b;
    b->a_ptr = a;  // 循环引用,a 和 b 永远不会释放
}