C++ 中的智能指针——std::shared_ptr 与 std::unique_ptr 的区别与使用场景

在现代 C++ 开发中,手动管理内存已逐渐被智能指针所取代。std::shared_ptrstd::unique_ptr 是最常用的两种智能指针,它们各自承担不同的职责,了解它们的区别与适用场景能够帮助我们写出更安全、更高效的代码。


1. 何为智能指针?

智能指针是一种包装普通裸指针的类,负责在对象生命周期结束时自动释放资源。与裸指针相比,智能指针可以避免内存泄漏、悬空指针等问题。

  • **`std::unique_ptr `**:实现单一所有权(ownership)。同一时间只能有一个 `unique_ptr` 指向同一块内存,指针移动后原对象失效。
  • **`std::shared_ptr `**:实现共享所有权(reference counting)。多个 `shared_ptr` 可以指向同一块内存,引用计数为 0 时自动销毁。

2. 关键区别

特性 unique_ptr shared_ptr
所有权 独占 共享
复制 只允许移动(std::move 允许复制,计数自增
性能 较低(无计数开销) 较高(计数读写)
线程安全 计数不安全,需外部同步 计数操作原子化,线程安全
用途 资源所有权不需要共享,适合 RAII 需要跨模块共享资源,或在容器中使用

3. 典型使用场景

3.1 unique_ptr

  • 资源拥有者:如打开的文件、网络套接字、内存缓冲区等。
  • 工厂函数返回值:让调用者获得资源所有权,避免裸指针返回。
  • 局部变量:在函数内部创建对象并在结束时自动销毁。
std::unique_ptr <Socket> create_socket() {
    return std::make_unique <Socket>(/* params */);
}

3.2 shared_ptr

  • 对象跨模块共享:如 GUI 事件系统、插件架构。
  • 链表/树等数据结构:节点可以共享同一子树。
  • 容器元素:std::vector<std::shared_ptr> 等。
void register_listener(std::shared_ptr <EventListener> listener) {
    listeners_.push_back(listener);
}

4. 代码示例

4.1 unique_ptr 的移动语义

std::unique_ptr <int> p1 = std::make_unique<int>(42);
std::unique_ptr <int> p2 = std::move(p1); // p1 现在为空

if (!p1) {
    std::cout << "p1 is now nullptr\n";
}

4.2 shared_ptr 的引用计数

std::shared_ptr <int> sp1 = std::make_shared<int>(10);
{
    std::shared_ptr <int> sp2 = sp1; // 引用计数 +1
    std::cout << "use_count: " << sp1.use_count() << '\n'; // 2
} // sp2 离开作用域,计数 -1

std::cout << "use_count after scope: " << sp1.use_count() << '\n'; // 1

4.3 自定义 deleter

auto deleter = [](FILE* fp) {
    if (fp) fclose(fp);
};

std::unique_ptr<FILE, decltype(deleter)> file_ptr(
    fopen("log.txt", "w"), deleter);

5. 性能与安全细节

  • 避免循环引用:在使用 shared_ptr 时,若两个对象互相持有 shared_ptr,会导致内存泄漏。可使用 std::weak_ptr 打破循环。
  • 自定义分配器:可以通过 std::allocator 或自定义 std::allocator 为智能指针分配内存。
  • 线程安全shared_ptr 的计数操作是原子化的,但对象本身的并发访问仍需同步。

6. 小结

  • std::unique_ptr 适用于需要独占资源的场景,性能最优,使用简单。
  • std::shared_ptr 适合需要共享资源的复杂结构,需注意循环引用与性能成本。
  • 正确选择智能指针,能够显著提升 C++ 代码的可维护性与安全性。

通过深入理解这两种智能指针的设计哲学与实际使用方法,程序员可以更好地控制资源生命周期,避免常见的内存错误,编写出更健壮、更高效的 C++ 程序。

发表评论