C++智能指针

智能指针

std::shared_ptr

  • 核心特性共享所有权的智能指针,多个shared_ptr可以指向同一个对象,内部通过引用计数(reference count)跟踪对象被引用的次数。当最后一个指向该对象的shared_ptr被销毁时,对象才会被自动删除。

  • 适用场景:需要多个所有者共享资源的场景,如容器中存储的对象、多个指针指向同一动态对象等。

  • 基本用法

    #include <memory>
    #include <iostream>
    
    int main() {
        // 创建指向int的shared_ptr(推荐使用make_shared,更高效)
        auto sptr1 = std::make_shared<int>(200);
        std::cout << "引用计数: " << sptr1.use_count() << std::endl; // 输出:1
    
        // 拷贝,引用计数增加
        std::shared_ptr<int> sptr2 = sptr1;
        std::cout << "引用计数: " << sptr1.use_count() << std::endl; // 输出:2
    
        // 重置sptr1,引用计数减少
        sptr1.reset();
        std::cout << "引用计数: " << sptr2.use_count() << std::endl; // 输出:1
    
        return 0; // sptr2销毁时,引用计数变为0,释放内存
    }
    
  • 注意:避免循环引用(如两个shared_ptr互相指向对方),否则引用计数永远不会归零,导致内存泄漏(需配合weak_ptr解决)。

std::unique_ptr

  • 核心特性独占所有权的智能指针,同一时间只能有一个unique_ptr指向某个对象,不允许拷贝(但允许移动)。当unique_ptr被销毁(如离开作用域)时,它所指向的对象会自动被删除。

  • 适用场景:管理独占所有权的资源,如动态分配的单个对象或数组,是替代auto_ptr的现代方案。

  • 基本用法

    #include <memory>
    
    int main() {
        // 创建指向int的unique_ptr
        std::unique_ptr<int> uptr1(new int(42));
        // 或使用更安全的make_unique(C++14引入)
        auto uptr2 = std::make_unique<int>(100);
    
        // 不允许拷贝
        // std::unique_ptr<int> uptr3 = uptr1; // 编译错误
    
        // 允许移动(所有权转移)
        std::unique_ptr<int> uptr4 = std::move(uptr1); // uptr1变为nullptr
    
        return 0; // uptr2、uptr4销毁时,自动释放所指向的内存
    }
    
  • 优势:轻量级(内存开销小),效率高,编译期检查所有权,避免意外拷贝。

std::weak_ptr

  • 核心特性弱引用智能指针,不拥有对象的所有权,仅作为shared_ptr的辅助工具。它指向shared_ptr管理的对象,但不增加引用计数,因此不会影响对象的生命周期。

  • 适用场景:解决shared_ptr的循环引用问题,或需要临时访问某个资源但不希望延长其生命周期的场景。

  • 基本用法

    #include <memory>
    #include <iostream>
    
    int main() {
        auto sptr = std::make_shared<int>(300);
        std::weak_ptr<int> wptr = sptr; // 弱引用,不增加引用计数
    
        std::cout << "sptr引用计数: " << sptr.use_count() << std::endl; // 输出:1
    
        // 通过lock()获取shared_ptr(若对象已释放,返回nullptr)
        if (auto temp = wptr.lock()) {
            std::cout << "对象值: " << *temp << std::endl; // 输出:300
        } else {
            std::cout << "对象已释放" << std::endl;
        }
    
        sptr.reset(); // 释放对象,引用计数变为0
        
        if (auto temp = wptr.lock()) {
            std::cout << "对象值: " << *temp << std::endl;
        } else {
            std::cout << "对象已释放" << std::endl; // 输出此句
        }
    
        return 0;
    }
    
  • 核心方法lock()用于获取一个指向对象的shared_ptr(若对象存在),expired()用于检查所指向的对象是否已被释放。

总结对比

智能指针 所有权 引用计数 主要用途 缺点
unique_ptr 独占 管理独占资源,替代原始指针 不可拷贝,仅可移动
shared_ptr 共享 多所有者共享资源 有额外内存开销,可能循环引用
weak_ptr 无(弱引用) 不影响 解决循环引用,临时访问资源 不能直接访问对象,需通过lock()

实际开发中,应根据资源的所有权关系选择合适的智能指针:优先使用unique_ptr(效率最高),需共享时使用shared_ptr,配合weak_ptr解决循环引用问题。

另外相比new,make_xxx更具有优越性.具体原因无需说明,单从概念即可看出,new的话要构造两次.

使用

智能指针能如普通指针一样使用,因为被重载了.

.get()获取原始指针

.release取消托管,需要自己手动释放.

.reset(xxx)重置指针托管的地址,即释放掉原本托管的内存,用参数智能代替,如xxx为空则是直接释放.

自定义删除器

RAII(Resource Acquisition Is Initialization)是由c++之父Bjarne Stroustrup提出的,中文翻译为资源获取即初始化.

我觉得这句很正确,其实就和我们强制写0是一样的,增加代码可控性.那么我们可以利用智能指针的特性来实现这个.

但是我们会发现智能指针设置得很自由,有些销毁方式不是单纯的delete能解决的,所以智能指针是有自定义删除器的功能的,有三种:

  • 函数指针
  • Lambda:无状态的 Lambda(不捕获任何变量)或空的函数对象作为删除器不会增加 unique_ptr 的大小,这是一种零成本抽象。但如果使用函数指针或者有状态的删除器,unique_ptr 的大小会增加。
  • 仿函数

std::unique_ptr<T, DeleterType>std::shared_ptr<T>不同,单从类型可以看出,如果unique要自定义删除器,要实例化的时候写上相应删除器类型.

std::uniquepointer

通常,当我们写 std::unique_ptr<T, Deleter> 时,unique_ptr 内部会认为它管理的是一个类型为 T* 的指针。它提供的成员函数,比如 get(),就会返回一个 T* 类型。

例如,对于 std::unique_ptr<int>,它内部管理的指针类型是 int*

但是问题出现了,如果当 T 本身就是句柄或指针类型时怎么办.

构建一个情景,THANDLE。在 Windows SDK 中,HANDLE 本身通常就是 void* 的一个别名。

如果我们不进行任何自定义,直接使用 std::unique_ptr<HANDLE, HandleDeleter>,那么 unique_ptr 会默认它管理的指针类型是 void**。这个问题不言而喻了.

故而在 std::unique_ptr<T, Deleter> 会检查其删除器类型 Deleter 中是否存在一个名为 pointer 的嵌套类型,来告诉它T应该是什么。当然这里只有仿函数可以实现了.

下例:

struct HandleDeleter {
    // 告诉 unique_ptr,你管理的“指针”类型其实是 HANDLE
    using pointer = HANDLE;
    void operator()(HANDLE handle) const {
        if (handle && handle != INVALID_HANDLE_VALUE) {
            CloseHandle(handle);
        }
    }
};

// T 是 HANDLE, Deleter 是 HandleDeleter
// 因为 HandleDeleter::pointer 存在,所以 UniqueHandle 内部的指针类型是 HANDLE,而不是 HANDLE*
using UniqueHandle = std::unique_ptr<HANDLE, HandleDeleter>;

这两点让unique_ptr可以实现RAII的包装.

posted @ 2025-08-30 13:53  T0fV404  阅读(5)  评论(0)    收藏  举报