前置知识: C++

智能指针详解

00:00
3 min Intermediate 2026/6/14

unique_ptr、shared_ptr与weak_ptr

概述

智能指针是 C++ 管理动态内存的核心工具,通过 RAII 机制自动释放资源,消除了手动 new/delete 带来的内存泄漏和悬空指针风险。C++ 标准库提供了三种智能指针:std::unique_ptr 实现独占所有权,std::shared_ptr 实现共享所有权,std::weak_ptr 解决共享所有权中的循环引用问题。

智能指针设计哲学是:通过类型系统表达所有权语义,让编译器帮助开发者正确管理

基础概念

三种智能指针对比

指针所有权引用计数开销适用场景
unique_ptr独占几乎为零默认选择
shared_ptr共享有(原子操作需要共享所有权
weak_ptr无(观察者增加打破循环引用

所有权原则

  • 默认使用 unique_ptr,只在需要共享时使用 shared_ptr
  • 所有权应尽量明确,避免模糊的共享关系
  • 使用 make_uniquemake_shared 创建智能指针,避免裸 new

快速上手

unique_ptr 独占所有权

#include <memory>
#include <iostream>

class Widget {
public:
    Widget() { std::cout << "Widget 构造" << std::endl; }
    ~Widget() { std::cout << "Widget 析构" << std::endl; }
    void use() { std::cout << "Widget 使用" << std::endl; }
};

int main() {
    // 创建 unique_ptr
    auto p = std::make_unique<Widget>();  // 推荐:异常安全

    // 使用
    p->use();
    std::cout << "地址: " << p.get() << std::endl;

    // 所有权转移(移动语义)
    auto q = std::move(p);  // p 变为 nullptr,q 获得所有权
    if (!p) std::cout << "p 已为空" << std::endl;

    // 离开作用域时 q 自动销毁 Widget
    return 0;
}

shared_ptr 共享所有权

#include <memory>
#include <iostream>

int main() {
    auto p1 = std::make_shared<int>(42);
    auto p2 = p1;  // 共享所有权,引用计数 +1

    std::cout << "值: " << *p1 << std::endl;          // 42
    std::cout << "引用计数: " << p1.use_count() << std::endl;  // 2

    p1.reset();  // p1 释放引用,引用计数 -1
    std::cout << "p2 引用计数: " << p2.use_count() << std::endl;  // 1
    // p2 仍有效,对象未销毁
    return 0;
}

weak_ptr 观察者

#include <memory>
#include <iostream>

int main() {
    auto sp = std::make_shared<int>(42);
    std::weak_ptr<int> wp = sp;  // 不增加引用计数

    std::cout << "引用计数: " << sp.use_count() << std::endl;  // 1

    // 安全访问
    if (auto locked = wp.lock()) {
        std::cout << "值: " << *locked << std::endl;  // 42
    }

    sp.reset();  // 销毁对象
    std::cout << "是否过期: " << wp.expired() << std::endl;  // true
    return 0;
}

详细用法

unique_ptr 与数组

#include <memory>

// 管理动态数组
auto arr = std::make_unique<int[]>(100);  // C++14
arr[0] = 42;
arr[1] = 100;

// 注意:unique_ptr<T[]> 不提供 operator* 和 operator->
// 只提供 operator[]

// 更推荐使用 std::vector 或 std::array

自定义删除器

#include <memory>
#include <cstdio>

// 使用自定义删除器管理 FILE*
auto file_closer = [](FILE* f) {
    if (f) {
        std::fclose(f);
        std::cout << "文件已关闭" << std::endl;
    }
};

using unique_file = std::unique_ptr<FILE, decltype(file_closer)>;

unique_file openFile(const char* path, const char* mode) {
    FILE* f = std::fopen(path, mode);
    if (!f) throw std::runtime_error("无法打开文件");
    return unique_file(f, file_closer);
}

// 管理 Windows HANDLE
#ifdef _WIN32
auto handle_closer = [](void* h) {
    if (h != INVALID_HANDLE_VALUE) CloseHandle(h);
};
std::unique_ptr<void, decltype(handle_closer)> win_handle(h, handle_closer);
#endif

shared_ptr 与别名构造

#include <memory>
#include <string>

struct Config {
    std::string host;
    int port;
    std::string database;
};

// shared_ptr 的别名构造:指向成员但共享整个对象的生命周期
std::shared_ptr<std::string> getHost(std::shared_ptr<Config> config) {
    // 创建指向 config->host 的 shared_ptr
    // 但引用计数与 config 共享
    return std::shared_ptr<std::string>(config, &config->host);
}

// 使用
auto cfg = std::make_shared<Config>(Config{"localhost", 3306, "mydb"});
auto host = getHost(cfg);
// cfg 和 host 共享引用计数,Config 不会被提前销毁

工厂函数返回智能指针

#include <memory>

class Shape {
public:
    virtual ~Shape() = default;
    virtual double area() const = 0;
};

class Circle : public Shape {
    double radius_;
public:
    explicit Circle(double r) : radius_(r) {}
    double area() const override { return 3.14159 * radius_ * radius_; }
};

class Rectangle : public Shape {
    double width_, height_;
public:
    Rectangle(double w, double h) : width_(w), height_(h) {}
    double area() const override { return width_ * height_; }
};

// 工厂函数返回 unique_ptr,所有权清晰
std::unique_ptr<Shape> createShape(const std::string& type, double a, double b = 0) {
    if (type == "circle") return std::make_unique<Circle>(a);
    if (type == "rectangle") return std::make_unique<Rectangle>(a, b);
    throw std::invalid_argument("未知形状");
}

常见场景

Pimpl 惯用法

// widget.h
class Widget {
public:
    Widget();
    ~Widget();
    void doWork();
private:
    struct Impl;
    std::unique_ptr<Impl> impl_;  // 隐藏实现细节
};

// widget.cpp
struct Widget::Impl {
    std::vector<int> data;
    void process() { /* 复杂实现 */ }
};

Widget::Widget() : impl_(std::make_unique<Impl>()) {}
Widget::~Widget() = default;  // 必须在实现文件中定义,因为 Impl 在此处完整
void Widget::doWork() { impl_->process(); }

异步回调中的安全引用

#include <memory>

class Service : public std::enable_shared_from_this<Service> {
public:
    void startAsync() {
        // 使用 shared_from_this() 确保回调执行时对象仍存活
        auto self = shared_from_this();
        std::thread([self]() {
            self->processResult();  // self 保持对象存活
        }).detach();
    }

    void processResult() { /* 处理结果 */ }
};

注意事项

  • 优先使用 make_uniquemake_shared 创建智能指针,避免裸 new 和潜在的内存泄漏
  • unique_ptr 不能拷贝,只能移动;如果需要拷贝语义,应考虑是否真的需要共享所有权
  • shared_ptr引用计数原子操作,有一定性能开销,不应在性能关键路径上频繁拷贝
  • 避免从裸指针创建shared_ptr,这会导致个独立的引用计数对象会被删除
  • weak_ptr::lock() 返回后应立即使用,不要存储后再使用,因为对象可能随时被销毁
  • 不要在构造函数调用 shared_from_this(),此时对象尚未被 shared_ptr 管理

进阶用法

shared_ptr 的线程安全性

#include <memory>
#include <mutex>

// shared_ptr 的线程安全规则:
// 1. 引用计数本身是线程安全的(原子操作)
// 2. 访问指向的对象不是线程安全的,需要额外同步
// 3. 不同的 shared_ptr 实例可以被不同线程同时读取

class ThreadSafeData {
    std::shared_ptr<const std::vector<int>> data_;
    std::mutex mtx_;
public:
    std::shared_ptr<const std::vector<int>> get() {
        std::lock_guard<std::mutex> lock(mtx_);
        return data_;  // 返回副本,线程安全
    }

    void update(std::shared_ptr<const std::vector<int>> newData) {
        std::lock_guard<std::mutex> lock(mtx_);
        data_ = std::move(newData);  // 原子替换
    }
};

intrusive_ptr 模式

// 当 shared_ptr 的控制块开销不可接受时,可以使用侵入式引用计数
class RefCounted {
    mutable std::atomic<int> refCount_{0};
public:
    void addRef() const { refCount_.fetch_add(1, std::memory_order_relaxed); }
    void release() const {
        if (refCount_.fetch_sub(1, std::memory_order_acq_rel) == 1) {
            delete this;
        }
    }
protected:
    virtual ~RefCounted() = default;
};

// 侵入式智能指针(简化版)
template<typename T>
class IntrusivePtr {
    T* ptr_;
public:
    explicit IntrusivePtr(T* p = nullptr) : ptr_(p) {
        if (ptr_) ptr_->addRef();
    }
    ~IntrusivePtr() { if (ptr_) ptr_->release(); }
    IntrusivePtr(const IntrusivePtr& other) : ptr_(other.ptr_) {
        if (ptr_) ptr_->addRef();
    }
    T* operator->() const { return ptr_; }
    T& operator*() const { return *ptr_; }
};

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式