C++ 中的智能指针:共享与独占的细节

在现代 C++ 开发中,智能指针已成为管理资源的核心工具。它们通过 RAII(资源获取即初始化)机制,保证了对象生命周期的一致性,显著降低了内存泄漏和悬挂指针的风险。本文将聚焦于两大主流智能指针:std::shared_ptrstd::unique_ptr,探讨它们在不同场景下的使用细节、优势与潜在陷阱,并给出实际编码示例。

1. std::unique_ptr——独占所有权的简洁实现

std::unique_ptr 表示独占式所有权,即同一时间只能有一个 unique_ptr 拥有某个对象。它提供了以下特性:

  • 无引用计数:性能开销极低,适合临时对象或内部资源管理。
  • 自动销毁:当 unique_ptr 离开作用域,持有的对象被 delete 释放。
  • 转移语义:通过 std::move 可以将所有权从一个指针转移到另一个。
#include <memory>
#include <iostream>

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

int main() {
    std::unique_ptr <Widget> p1(new Widget); // 自动析构
    std::unique_ptr <Widget> p2 = std::move(p1); // 所有权转移
    if (!p1) std::cout << "p1 is empty\n";
}

1.1 自定义删除器

unique_ptr 可以接受自定义删除器,方便与非标准分配器或资源类型配合使用。

struct FileDeleter {
    void operator()(FILE* f) const {
        if (f) fclose(f);
    }
};

using FilePtr = std::unique_ptr<FILE, FileDeleter>;

FilePtr fp(fopen("log.txt", "w"), FileDeleter{});

2. std::shared_ptr——引用计数的共享所有权

std::shared_ptr 允许多个指针实例共享同一对象,并维护内部引用计数。其特点包括:

  • 线程安全的引用计数:在多线程环境下,计数自增/自减是原子操作。
  • 延迟销毁:对象在最后一个 shared_ptr 被销毁时才被释放。
  • 弱引用std::weak_ptr 允许查看对象但不参与计数,避免循环引用。
#include <memory>
#include <iostream>

int main() {
    auto sp1 = std::make_shared <int>(42);
    std::weak_ptr <int> wp = sp1; // 不计数

    if (auto sp2 = wp.lock()) { // 获取共享指针
        std::cout << "Value: " << *sp2 << '\n';
    }
}

2.1 循环引用的危害

当两个对象通过 shared_ptr 互相引用时,引用计数永远不为零,导致内存泄漏。使用 weak_ptr 可以打破循环。

struct B; // 前向声明

struct A {
    std::shared_ptr <B> bPtr;
};

struct B {
    std::weak_ptr <A> aPtr; // 使用 weak_ptr
};

3. 与标准容器的配合

大多数标准容器(如 std::vectorstd::map)都能存放 unique_ptrshared_ptr,但需注意:

  • vector<unique_ptr<T>> 需要自定义移动构造函数或使用 std::move
  • map<key, shared_ptr<T>> 可以轻松实现多路复用资源。
std::vector<std::unique_ptr<Widget>> widgets;
widgets.emplace_back(std::make_unique <Widget>());

4. 性能考虑

智能指针 内存占用 计数管理 典型场景
unique_ptr 1 个指针 栈内对象、临时资源
shared_ptr 1 个指针 + 计数器 原子操作 需要共享、跨线程共享

在性能敏感的代码路径上,尽量使用 unique_ptr。只有在明确需要共享所有权时才引入 shared_ptr,并避免不必要的引用计数操作。

5. 小结

  • unique_ptr:轻量、独占、不可复制;适用于绝大多数资源管理。
  • shared_ptr:可复制、引用计数、线程安全;适用于真正需要共享所有权的场景。
  • weak_ptr:避免循环引用,提供“观察者”模式。

通过合理选择与组合这些智能指针,你可以写出更安全、可维护、且性能友好的 C++ 代码。

发表评论