C++ 中的智能指针使用技巧与最佳实践

在 C++11 之后,智能指针成为管理动态内存和资源的核心工具。它们帮助开发者避免内存泄漏、悬空指针以及其他与手动 new/delete 相关的错误。本文将从 std::unique_ptrstd::shared_ptrstd::weak_ptr 的使用场景、常见坑以及提升代码可读性与性能的技巧展开讨论。

1. std::unique_ptr——单一所有权的精髓

1.1 基础使用

std::unique_ptr <MyClass> ptr(new MyClass());

或更推荐的 make_unique

auto ptr = std::make_unique <MyClass>();

unique_ptr 只能被移动,不能复制,确保在同一时间只有一个指针拥有资源。

1.2 自定义删除器

struct FileCloser {
    void operator()(FILE* fp) const {
        if (fp) fclose(fp);
    }
};

auto file = std::unique_ptr<FILE, FileCloser>(fopen("log.txt", "w"), FileCloser());

自定义删除器可用于管理非 new 分配的资源。

1.3 与容器结合

std::vector<std::unique_ptr<Item>> items;
items.emplace_back(std::make_unique <Item>());

容器内部会自动调用删除器,避免忘记 delete

2. std::shared_ptr——多重所有权与引用计数

2.1 基础使用

auto sp1 = std::make_shared <Widget>();
auto sp2 = sp1; // 引用计数 +1

引用计数采用原子操作,适合多线程环境。

2.2 循环引用警惕

struct Node {
    std::shared_ptr <Node> next;
    std::shared_ptr <Node> prev;
};

上述两者互相持有 shared_ptr,导致计数永不归零,内存泄漏。解决方案是将其中一个改为 weak_ptr

2.3 与 std::weak_ptr 结合

std::weak_ptr <Widget> weak = sp1;
if (auto locked = weak.lock()) {
    // 成功获取 shared_ptr,使用对象
}

weak_ptr 不参与引用计数,避免循环引用。

3. std::weak_ptr——观察者模式的实现

weak_ptr 主要用于观察者模式、缓存等场景,保持对对象的“非拥有”访问。

class Subject {
public:
    void registerObserver(std::shared_ptr <Observer> obs) {
        observers_.push_back(obs);
    }
    void notify() {
        for (auto it = observers_.begin(); it != observers_.end(); ) {
            if (auto obs = it->lock()) {
                obs->update();
                ++it;
            } else {
                it = observers_.erase(it); // 已被销毁的观察者移除
            }
        }
    }
private:
    std::vector<std::weak_ptr<Observer>> observers_;
};

4. 组合使用:RAII 与智能指针

RAII(Resource Acquisition Is Initialization)与智能指针天然契合。将资源包装在 RAII 对象中,可让异常安全与自动释放同时实现。

class FileHandle {
public:
    explicit FileHandle(const std::string& path)
        : file_(fopen(path.c_str(), "r")) {
        if (!file_) throw std::runtime_error("open failed");
    }
    ~FileHandle() { if (file_) fclose(file_); }
    FILE* get() const { return file_; }
private:
    FILE* file_;
};

void readFile() {
    FileHandle fh("data.txt");
    // 读取逻辑
}

5. 性能提示

  1. 避免不必要的复制unique_ptr 只允许移动,使用 std::move
  2. 自定义删除器与内存池:自定义删除器可集成内存池,提升分配速度。
  3. 使用 make_unique/make_shared:一次性分配对象与计数器,减少内存碎片。
  4. 慎用 shared_ptr:引用计数开销不容忽视,只有在真正需要多重所有权时才使用。

6. 常见错误与调试技巧

  • 悬空 weak_ptr:尝试 lock() 可能返回空,需始终检查。
  • 忘记 std::moveunique_ptr 直接赋值会报错,必须显式移动。
  • 循环引用:使用 shared_ptrweak_ptr 组合,避免内存泄漏。
  • 异常安全:使用智能指针可以显著降低资源泄漏风险,保持代码简洁。

7. 小结

  • unique_ptr 适合独占所有权,适用于大多数情况。
  • shared_ptr 用于多所有权,需防止循环引用。
  • weak_ptr 用于观察者或缓存,避免所有权持有。
  • 与 RAII 结合,实现异常安全与资源管理。

通过合理选择与组合上述智能指针,C++ 开发者可以编写更安全、更易维护、性能更高的代码。祝你在编码旅程中愉快地驾驭智能指针的力量!

发表评论