C++智能指针Skill cpp-smart-pointers

掌握C++智能指针和RAII模式,实现自动、异常安全的资源管理。本技能涵盖unique_ptr、shared_ptr、weak_ptr、自定义删除器以及现代C++内存管理的最佳实践。关键词:C++智能指针、RAII、内存管理、资源管理、unique_ptr、shared_ptr、weak_ptr、异常安全。

架构设计 0 次安装 0 次浏览 更新于 3/25/2026

名称: cpp-smart-pointers 用户可调用: false 描述: 用于在C++中使用智能指针(包括unique_ptr、shared_ptr、weak_ptr和RAII模式)安全地管理内存。 允许工具:

  • Bash
  • 读取
  • 写入
  • 编辑

C++智能指针和RAII

掌握C++智能指针和资源获取即初始化(RAII)模式,实现自动、异常安全的资源管理。本技能涵盖unique_ptr、shared_ptr、weak_ptr、自定义删除器以及现代C++内存管理的最佳实践。

RAII原则

资源获取即初始化是C++的基本习惯用法,将资源生命周期绑定到对象生命周期。

核心概念

// 坏:手动资源管理
void process_file_bad() {
    FILE* file = fopen("data.txt", "r");
    if (!file) return;

    // ... 处理文件 ...
    // 如果发生异常,文件永远不会关闭!

    fclose(file);
}

// 好:使用智能指针的RAII
void process_file_good() {
    auto deleter = [](FILE* f) { if (f) fclose(f); };
    std::unique_ptr<FILE, decltype(deleter)> file(fopen("data.txt", "r"), deleter);

    if (!file) return;

    // ... 处理文件 ...
    // 当unique_ptr销毁时,文件自动关闭
}

// 更好:自定义RAII包装器
class FileHandle {
    FILE* file;
public:
    explicit FileHandle(const char* filename, const char* mode)
        : file(fopen(filename, mode)) {
        if (!file) throw std::runtime_error("文件打开失败");
    }

    ~FileHandle() {
        if (file) fclose(file);
    }

    // 删除复制操作
    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;

    // 允许移动操作
    FileHandle(FileHandle&& other) noexcept : file(other.file) {
        other.file = nullptr;
    }

    FileHandle& operator=(FileHandle&& other) noexcept {
        if (this != &other) {
            if (file) fclose(file);
            file = other.file;
            other.file = nullptr;
        }
        return *this;
    }

    FILE* get() const { return file; }
};

RAII优势

// 异常安全
void transaction() {
    std::lock_guard<std::mutex> lock(mutex); // RAII锁
    std::unique_ptr<Resource> resource = acquire_resource(); // RAII内存

    // 如果抛出异常,锁自动释放,内存自动释放
    risky_operation();
}

// 所有路径中的自动清理
std::unique_ptr<int[]> create_buffer(size_t size) {
    auto buffer = std::make_unique<int[]>(size);

    if (size > max_size) {
        return nullptr; // 缓冲区清理
    }

    initialize(buffer.get(), size);
    return buffer; // 所有权转移
}

Unique Ptr

std::unique_ptr提供对动态分配对象的独占所有权。

Unique Ptr基本用法

#include <memory>

// 创建unique_ptr
std::unique_ptr<int> ptr1(new int(42));
auto ptr2 = std::make_unique<int>(100); // 首选(C++14)

// 数组unique_ptr
std::unique_ptr<int[]> arr(new int[10]);
auto arr2 = std::make_unique<int[]>(10); // 首选

// 自定义类型
class MyClass {
public:
    MyClass(int x, std::string s) : value(x), name(s) {}
    void print() const { std::cout << name << ": " << value << std::endl; }
private:
    int value;
    std::string name;
};

auto obj = std::make_unique<MyClass>(42, "测试");
obj->print();

所有权转移

// Unique_ptr只能移动,不能复制
std::unique_ptr<int> ptr1 = std::make_unique<int>(42);

// std::unique_ptr<int> ptr2 = ptr1; // 错误:复制被删除

// 移动所有权
std::unique_ptr<int> ptr2 = std::move(ptr1);
// ptr1现在为nullptr,ptr2拥有资源

// 函数接受所有权
void consume(std::unique_ptr<int> ptr) {
    std::cout << *ptr << std::endl;
    // ptr在这里销毁,资源删除
}

consume(std::move(ptr2)); // 将所有权转移给函数

// 函数返回所有权
std::unique_ptr<int> create() {
    auto ptr = std::make_unique<int>(100);
    return ptr; // 移动语义,无需显式std::move
}

auto result = create(); // 所有权转移给result

自定义删除器

// 函数指针删除器
void custom_delete(int* ptr) {
    std::cout << "删除: " << *ptr << std::endl;
    delete ptr;
}

std::unique_ptr<int, decltype(&custom_delete)> ptr(new int(42), custom_delete);

// Lambda删除器
auto deleter = [](int* ptr) {
    std::cout << "Lambda删除: " << *ptr << std::endl;
    delete ptr;
};

std::unique_ptr<int, decltype(deleter)> ptr2(new int(100), deleter);

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

std::unique_ptr<FILE, decltype(file_deleter)> file(
    fopen("data.txt", "r"),
    file_deleter
);

// 套接字使用自定义删除器
struct SocketDeleter {
    void operator()(int* socket) const {
        if (socket && *socket >= 0) {
            close(*socket);
            delete socket;
        }
    }
};

std::unique_ptr<int, SocketDeleter> socket(new int(create_socket()));

Unique Ptr操作

std::unique_ptr<int> ptr = std::make_unique<int>(42);

// 访问
int value = *ptr;         // 解引用
int* raw = ptr.get();     // 获取原始指针(不转移所有权)

// 检查是否拥有对象
if (ptr) {
    std::cout << "拥有资源" << std::endl;
}

// 释放所有权(返回原始指针,unique_ptr变为nullptr)
int* released = ptr.release();
// 必须手动删除释放的指针
delete released;

// 重置(删除当前对象,可选地获取新对象的所有权)
ptr.reset();                    // 删除并变为nullptr
ptr.reset(new int(100));        // 删除旧的,拥有新的

// 交换
std::unique_ptr<int> ptr1 = std::make_unique<int>(1);
std::unique_ptr<int> ptr2 = std::make_unique<int>(2);
ptr1.swap(ptr2);
// 或
std::swap(ptr1, ptr2);

Shared Ptr

std::shared_ptr提供共享所有权,带有自动引用计数。

Shared Ptr基本用法

#include <memory>

// 创建shared_ptr
std::shared_ptr<int> ptr1(new int(42));
auto ptr2 = std::make_shared<int>(100); // 首选(更高效)

// 共享所有权
auto ptr3 = ptr2; // 引用计数 = 2
auto ptr4 = ptr2; // 引用计数 = 3

std::cout << "使用计数: " << ptr2.use_count() << std::endl; // 3

// 最后一个shared_ptr销毁时删除对象
{
    auto ptr5 = ptr2; // 引用计数 = 4
} // ptr5销毁,引用计数 = 3

Make Shared

// 优先使用make_shared而非new
auto ptr1 = std::make_shared<MyClass>(arg1, arg2);

// 为什么?单次分配而非两次:
// new: 分配对象 + 单独控制块
// make_shared: 单次分配两者

// 异常安全
func(std::shared_ptr<int>(new int(1)), std::shared_ptr<int>(new int(2))); // 有风险
func(std::make_shared<int>(1), std::make_shared<int>(2)); // 安全

// 数组支持(C++17及以后,因实现而异)
std::shared_ptr<int[]> arr(new int[10]);
// 注意:C++20添加了make_shared数组支持

Shared Ptr操作

std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
std::shared_ptr<int> ptr2 = ptr1;

// 访问
int value = *ptr1;
int* raw = ptr1.get();

// 引用计数
std::cout << "计数: " << ptr1.use_count() << std::endl;
std::cout << "唯一: " << ptr1.unique() << std::endl; // 如果计数 == 1则为true

// 检查是否拥有对象
if (ptr1) {
    std::cout << "拥有资源" << std::endl;
}

// 重置
ptr1.reset();                    // 减少引用计数,变为nullptr
ptr1.reset(new int(100));        // 减少旧引用计数,拥有新对象
ptr1 = nullptr;                  // 同reset()

// 交换
ptr1.swap(ptr2);
std::swap(ptr1, ptr2);

别名构造函数

struct Data {
    int x;
    int y;
};

auto data = std::make_shared<Data>();
data->x = 10;
data->y = 20;

// 创建指向成员的shared_ptr,但共享整个对象的所有权
std::shared_ptr<int> x_ptr(data, &data->x);
std::shared_ptr<int> y_ptr(data, &data->y);

// data的引用计数为3
// 当data、x_ptr和y_ptr都销毁时,Data对象删除

Weak Ptr

std::weak_ptr提供对shared_ptr管理对象的非拥有引用。

Weak Ptr基本用法

std::shared_ptr<int> shared = std::make_shared<int>(42);
std::weak_ptr<int> weak = shared; // 弱引用,不增加引用计数

std::cout << "共享计数: " << shared.use_count() << std::endl; // 1
std::cout << "弱引用计数: " << weak.use_count() << std::endl;     // 1

// 检查对象是否仍存在
if (!weak.expired()) {
    // 尝试获取shared_ptr
    if (auto locked = weak.lock()) {
        std::cout << "值: " << *locked << std::endl;
        // locked是shared_ptr,安全使用
    }
}

// 共享销毁后
shared.reset();
if (weak.expired()) {
    std::cout << "对象不再存在" << std::endl;
}

打破循环引用

// 问题:循环引用导致内存泄漏
struct Node {
    std::shared_ptr<Node> next;
    ~Node() { std::cout << "节点销毁" << std::endl; }
};

void memory_leak() {
    auto node1 = std::make_shared<Node>();
    auto node2 = std::make_shared<Node>();

    node1->next = node2;
    node2->next = node1; // 循环引用!

    // node1和node2超出作用域,但对象永远不会删除
    // 引用计数永远不会归零
}

// 解决方案:使用weak_ptr进行反向引用
struct NodeFixed {
    std::shared_ptr<NodeFixed> next;
    std::weak_ptr<NodeFixed> prev; // 用weak_ptr打破循环

    ~NodeFixed() { std::cout << "NodeFixed销毁" << std::endl; }
};

void no_leak() {
    auto node1 = std::make_shared<NodeFixed>();
    auto node2 = std::make_shared<NodeFixed>();

    node1->next = node2;
    node2->prev = node1; // weak_ptr不增加引用计数

    // 当shared_ptr销毁时,对象正确删除
}

观察者模式

class Subject;

class Observer {
    std::weak_ptr<Subject> subject;
public:
    void observe(std::shared_ptr<Subject> s) {
        subject = s;
    }

    void check() {
        if (auto s = subject.lock()) {
            std::cout << "主体仍存在" << std::endl;
            // 安全使用s
        } else {
            std::cout << "主体销毁" << std::endl;
        }
    }
};

class Subject {
public:
    void do_something() {
        std::cout << "主体做某事" << std::endl;
    }
};

// 用法
auto observer = std::make_shared<Observer>();
{
    auto subject = std::make_shared<Subject>();
    observer->observe(subject);
    observer->check(); // 主体存在
}
observer->check(); // 主体销毁

缓存模式

class ResourceCache {
    std::unordered_map<std::string, std::weak_ptr<Resource>> cache;

public:
    std::shared_ptr<Resource> get(const std::string& key) {
        // 尝试从缓存获取
        auto it = cache.find(key);
        if (it != cache.end()) {
            if (auto resource = it->second.lock()) {
                return resource; // 缓存命中
            } else {
                cache.erase(it); // 过期条目
            }
        }

        // 缓存未命中:加载资源
        auto resource = std::make_shared<Resource>(load_resource(key));
        cache[key] = resource; // 存储弱引用
        return resource;
    }

    void cleanup() {
        // 移除过期条目
        for (auto it = cache.begin(); it != cache.end(); ) {
            if (it->second.expired()) {
                it = cache.erase(it);
            } else {
                ++it;
            }
        }
    }
};

自定义删除器和分配器

高级删除器模式

// 日志删除器
template<typename T>
struct LoggingDeleter {
    void operator()(T* ptr) const {
        std::cout << "删除对象在 " << ptr << std::endl;
        delete ptr;
    }
};

std::unique_ptr<int, LoggingDeleter<int>> ptr(new int(42));

// unique_ptr的数组删除器
template<typename T>
struct ArrayDeleter {
    void operator()(T* ptr) const {
        delete[] ptr;
    }
};

std::unique_ptr<int, ArrayDeleter<int>> arr(new int[10]);

// 条件删除器
template<typename T>
class ConditionalDeleter {
    bool should_delete;
public:
    explicit ConditionalDeleter(bool del = true) : should_delete(del) {}

    void operator()(T* ptr) const {
        if (should_delete) {
            delete ptr;
        }
    }
};

// 资源池删除器
template<typename T>
class PoolDeleter {
    std::shared_ptr<ResourcePool<T>> pool;
public:
    explicit PoolDeleter(std::shared_ptr<ResourcePool<T>> p) : pool(p) {}

    void operator()(T* ptr) const {
        pool->return_to_pool(ptr); // 返回到池而不是删除
    }
};

自定义分配器

// shared_ptr的自定义分配器
template<typename T>
class TrackingAllocator {
public:
    using value_type = T;

    TrackingAllocator() = default;

    template<typename U>
    TrackingAllocator(const TrackingAllocator<U>&) {}

    T* allocate(std::size_t n) {
        std::cout << "分配 " << n << " 个对象" << std::endl;
        return static_cast<T*>(::operator new(n * sizeof(T)));
    }

    void deallocate(T* ptr, std::size_t n) {
        std::cout << "释放 " << n << " 个对象" << std::endl;
        ::operator delete(ptr);
    }
};

// 与shared_ptr的用法
auto ptr = std::allocate_shared<int>(TrackingAllocator<int>(), 42);

智能指针转换

安全转换

// unique_ptr到shared_ptr(所有权转移)
std::unique_ptr<int> unique = std::make_unique<int>(42);
std::shared_ptr<int> shared = std::move(unique); // unique现在为nullptr

// shared_ptr到weak_ptr
std::weak_ptr<int> weak = shared;

// weak_ptr到shared_ptr(带空值检查)
if (auto locked = weak.lock()) {
    // 使用锁定的shared_ptr
}

// 原始指针到shared_ptr(危险 - 见陷阱)
int* raw = new int(42);
// std::shared_ptr<int> shared(raw); // 危险!

向下转换与智能指针

class Base {
public:
    virtual ~Base() = default;
    virtual void foo() = 0;
};

class Derived : public Base {
public:
    void foo() override {}
    void bar() {}
};

// static_pointer_cast(类似static_cast)
std::shared_ptr<Base> base = std::make_shared<Derived>();
std::shared_ptr<Derived> derived = std::static_pointer_cast<Derived>(base);

// dynamic_pointer_cast(类似dynamic_cast,失败时返回nullptr)
std::shared_ptr<Base> base2 = std::make_shared<Derived>();
if (auto derived2 = std::dynamic_pointer_cast<Derived>(base2)) {
    derived2->bar(); // 安全调用Derived方法
}

// const_pointer_cast(类似const_cast)
std::shared_ptr<const int> const_ptr = std::make_shared<const int>(42);
std::shared_ptr<int> mutable_ptr = std::const_pointer_cast<int>(const_ptr);

性能考虑

内存开销

// sizeof比较
sizeof(int*)                           // 8字节(64位)
sizeof(std::unique_ptr<int>)           // 8字节(同原始指针)
sizeof(std::shared_ptr<int>)           // 16字节(指针 + 控制块指针)
sizeof(std::weak_ptr<int>)             // 16字节(同shared_ptr)

// shared_ptr的控制块开销
// 包含:引用计数、弱引用计数、删除器、分配器
// 大小可变,通常24-32字节

// make_shared vs new for shared_ptr
auto ptr1 = std::make_shared<int>(42);     // 1次分配
std::shared_ptr<int> ptr2(new int(42));    // 2次分配

性能优化

// 可能时优先使用unique_ptr
std::unique_ptr<Resource> create_resource() {
    return std::make_unique<Resource>();
}

// 仅在需要时转换为shared_ptr
auto unique = create_resource();
std::shared_ptr<Resource> shared = std::move(unique);

// 避免不必要的shared_ptr复制
void process(const std::shared_ptr<Resource>& res) { // 通过const引用传递
    // 使用res,不增加引用计数
}

// 转移所有权时移动
std::shared_ptr<Resource> transfer(std::shared_ptr<Resource> res) {
    return res; // RVO或移动
}

// 使用weak_ptr进行非拥有引用
class Observer {
    std::weak_ptr<Subject> subject; // 不增加引用计数
};

异常安全

强异常保证

class ExceptionSafe {
    std::unique_ptr<Resource1> res1;
    std::unique_ptr<Resource2> res2;

public:
    void update(int value) {
        // 创建新资源
        auto new_res1 = std::make_unique<Resource1>(value);
        auto new_res2 = std::make_unique<Resource2>(value);

        // 如果上面抛出异常,不进行任何更改(强保证)

        // 提交更改(noexcept操作)
        res1 = std::move(new_res1);
        res2 = std::move(new_res2);
    }
};

RAII用于事务

class Transaction {
    std::unique_ptr<Connection> conn;
    bool committed = false;

public:
    explicit Transaction(std::unique_ptr<Connection> c)
        : conn(std::move(c)) {
        conn->begin_transaction();
    }

    ~Transaction() {
        if (!committed) {
            try {
                conn->rollback();
            } catch (...) {
                // 记录错误,不从析构函数抛出
            }
        }
    }

    void commit() {
        conn->commit();
        committed = true;
    }
};

// 用法
void perform_transaction() {
    auto conn = std::make_unique<Connection>();
    Transaction txn(std::move(conn));

    // 执行工作
    // 如果抛出异常,事务自动回滚

    txn.commit(); // 成功时显式提交
}

容器中的智能指针

智能指针向量

// unique_ptr向量
std::vector<std::unique_ptr<Widget>> widgets;

// 添加元素(必须移动)
widgets.push_back(std::make_unique<Widget>(1));
widgets.push_back(std::make_unique<Widget>(2));

// 不能复制向量
// auto vec2 = widgets; // 错误

// 可以移动向量
auto vec2 = std::move(widgets); // widgets现在为空

// 迭代
for (const auto& widget : vec2) {
    widget->process();
}

// 移除元素(自动删除)
vec2.erase(vec2.begin());

// shared_ptr向量
std::vector<std::shared_ptr<Widget>> shared_widgets;
shared_widgets.push_back(std::make_shared<Widget>(1));

// 可以复制向量(增加引用计数)
auto shared_vec2 = shared_widgets;

映射与智能指针

// 映射使用unique_ptr值
std::map<std::string, std::unique_ptr<Resource>> resource_map;

// 插入
resource_map["key1"] = std::make_unique<Resource>(1);
resource_map.emplace("key2", std::make_unique<Resource>(2));

// 查找和使用
auto it = resource_map.find("key1");
if (it != resource_map.end()) {
    it->second->process();
}

// 提取所有权
auto extracted = std::move(resource_map["key1"]);
resource_map.erase("key1");

// 映射使用shared_ptr进行共享所有权
std::map<std::string, std::shared_ptr<Resource>> shared_map;
shared_map["key"] = std::make_shared<Resource>(1);

// 多个映射可以共享同一资源
std::map<std::string, std::shared_ptr<Resource>> shared_map2;
shared_map2["key"] = shared_map["key"]; // 共享所有权

常见模式

工厂模式

class Product {
public:
    virtual ~Product() = default;
    virtual void use() = 0;
};

class ConcreteProductA : public Product {
public:
    void use() override { std::cout << "使用A" << std::endl; }
};

class ConcreteProductB : public Product {
public:
    void use() override { std::cout << "使用B" << std::endl; }
};

class Factory {
public:
    static std::unique_ptr<Product> create(const std::string& type) {
        if (type == "A") {
            return std::make_unique<ConcreteProductA>();
        } else if (type == "B") {
            return std::make_unique<ConcreteProductB>();
        }
        return nullptr;
    }
};

// 用法
auto product = Factory::create("A");
if (product) {
    product->use();
}

Pimpl习惯用法

// Widget.h
class Widget {
public:
    Widget();
    ~Widget();

    // 必须在头文件中声明但不定定义
    Widget(Widget&&) noexcept;
    Widget& operator=(Widget&&) noexcept;

    void do_something();

private:
    class Impl; // 前向声明
    std::unique_ptr<Impl> pimpl;
};

// Widget.cpp
class Widget::Impl {
public:
    void do_something_impl() {
        // 隐藏实现细节
    }

private:
    // 不在公共头文件中的私有成员
    std::vector<int> data;
    std::string name;
};

Widget::Widget() : pimpl(std::make_unique<Impl>()) {}

// 在.cpp中定义析构函数,在Impl完成之后
Widget::~Widget() = default;

Widget::Widget(Widget&&) noexcept = default;
Widget& Widget::operator=(Widget&&) noexcept = default;

void Widget::do_something() {
    pimpl->do_something_impl();
}

单例模式

class Singleton {
public:
    static Singleton& instance() {
        static Singleton instance; // C++11中线程安全
        return instance;
    }

    // 删除复制和移动
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    Singleton(Singleton&&) = delete;
    Singleton& operator=(Singleton&&) = delete;

    void do_something() {
        std::cout << "单例方法" << std::endl;
    }

private:
    Singleton() = default;
    ~Singleton() = default;
};

// 替代方案:智能指针用于显式控制
class ManagedSingleton {
public:
    static std::shared_ptr<ManagedSingleton> instance() {
        static auto inst = std::make_shared<ManagedSingleton>(PrivateTag{});
        return inst;
    }

private:
    struct PrivateTag {};
public:
    explicit ManagedSingleton(PrivateTag) {}
};

最佳实践

  1. 优先使用make_unique和make_shared:比直接使用new更高效且异常安全
  2. 默认使用unique_ptr:仅在确实需要共享所有权时使用shared_ptr
  3. 通过const引用传递智能指针:避免shared_ptr不必要的引用计数变化
  4. 使用weak_ptr打破循环:防止来自循环shared_ptr引用的内存泄漏
  5. 通过值返回进行所有权转移:让移动语义处理高效转移
  6. 切勿从同一原始指针创建多个shared_ptrs:导致双重删除
  7. 非内存资源的自定义删除器:用于文件、套接字、互斥锁等
  8. 标记移动操作为noexcept:在标准容器中启用优化
  9. 在容器中使用智能指针:允许多态对象容器
  10. 不要混合智能指针与原始指针所有权:选择一个所有权模型

常见陷阱

  1. 从原始this指针创建shared_ptr:改用enable_shared_from_this
  2. 循环shared_ptr引用:使用weak_ptr进行反向引用或父指针
  3. 从同一原始指针创建多个shared_ptrs:导致双重删除
  4. 使用get()创建新智能指针:破坏所有权模型
  5. 忘记使用move与unique_ptr:unique_ptr不可复制
  6. 混合智能指针与手动删除:一致使用一个所有权模型
  7. 当unique_ptr足够时使用shared_ptr:不必要的开销
  8. 不检查weak_ptr.lock()返回值:如果对象删除,可能返回nullptr
  9. 自定义删除器问题:错误的删除器类型或未处理nullptr
  10. 智能指针切片:存储基类指针以保持多态性

何时使用

使用此技能当:

  • 在C++中管理动态分配的内存
  • 实现资源管理的RAII模式
  • 在容器中使用多态对象
  • 防止内存泄漏和悬空指针
  • 实现异常安全代码
  • 创建工厂模式或对象层次结构
  • 使用引用计数管理共享资源
  • 用弱引用打破循环依赖
  • 用自动清理包装C API
  • 教学或学习现代C++内存管理

资源