C++11----智能指针

一、智能指针的用途与传统指针的问题

问题场景:手动内存管理的隐患

int div() {
    int a, b;
    cin >> a >> b;
    if (b == 0)
        throw invalid_argument("除0错误");
    return a / b;
}

// 手动管理内存时,异常可能导致资源泄漏
void f() {
    pair<string, string>* p1 = new pair<string, string>;
    pair<string, string>* p2 = new pair<string, string>;
    pair<string, string>* p3 = new pair<string, string>;
    pair<string, string>* p4 = new pair<string, string>;

    try {
        div(); // 若此处抛出异常,p2/p3/p4未释放
    } catch (...) {
        delete p1; // 仅释放p1,其他指针泄漏
        cout << "delete:" << p1 << endl;
        throw;
    }
    delete p1; // 重复释放p1(若div未抛异常)
    cout << "delete:" << p1 << endl;
}

核心问题:手动new/delete在异常场景下易导致内存泄漏或重复释放。

二、自定义智能指针(基础实现)

RAII 思想:资源由对象生命周期管理

template<class T>
class SmartPtr {
public:
    SmartPtr(T* ptr) : _ptr(ptr) {} // 构造时获取资源
    ~SmartPtr() { // 析构时释放资源
        cout << "delete:" << _ptr << endl;
        delete _ptr;
    }
    T& operator*() { return *_ptr; } // 行为像指针
    T* operator->() { return _ptr; }
private:
    T* _ptr;
};

// 使用示例:自动释放资源,无需手动delete
void f() {
    SmartPtr<pair<string, string>> sp1(new pair<string, string>("1111", "22222"));
    SmartPtr<pair<string, string>> sp2(new pair<string, string>);
    SmartPtr<string> sp4(new string("xxxxx"));
    cout << *sp4 << endl;         // 解引用
    cout << sp1->first << endl;   // 指针访问
    div(); // 若抛异常,sp1/sp2/sp4析构时自动释放
}

浅拷贝问题:未自定义赋值导致内存泄漏

//浅拷贝问题:SmartPtr未自定义赋值导致内存泄漏
int main() {
    SmartPtr<string> sp1(new string("xxxxx"));
    SmartPtr<string> sp2(new string("yyyyy"));
    sp1 = sp2; // 浅拷贝:两个对象指向同一地址,析构时重复释放,或原资源未释放
    return 0;
}

三、C++98 auto_ptr(已过时,不推荐使用)

设计缺陷:管理权转移导致悬空指针

class A {
public:
    A(int a = 0) : _a(a) { cout << "A(int a = 0)" << endl; }
    ~A() { cout << this << " ~A()" << endl; }
    int _a;
};

int main() {
    auto_ptr<A> ap1(new A(1));
    auto_ptr<A> ap3(ap1); // 管理权转移:ap1._ptr置空,ap3获取资源
    ap1->_a++; // 崩溃!ap1指向nullptr(悬空指针)
    return 0;
}

auto_ptr模拟实现(管理权转移逻辑)

template<class T>
class auto_ptr {
public:
    auto_ptr(T* ptr) : _ptr(ptr) {}
    ~auto_ptr() { delete _ptr; }
    // 拷贝构造时转移管理权
    auto_ptr(auto_ptr<T>& ap) : _ptr(ap._ptr) { ap._ptr = nullptr; }

    T& operator*() {
        return *_ptr;
    }
    T* operator->() {
        return _ptr;
    }
private:
    T* _ptr;
};

四、C++11 unique_ptr(独占所有权,禁止拷贝)

设计原则:简单粗暴,禁止拷贝 / 赋值

int main() {
    unique_ptr<A> up1(new A(1));
    // unique_ptr<A> up3(up1);       // 编译错误:拷贝构造被删除
    // up1 = up2;                    // 编译错误:赋值运算符被删除
    return 0;
}

模拟实现(删除拷贝构造和赋值运算符)

template<class T>
class unique_ptr {
public:
    unique_ptr(T* ptr) : _ptr(ptr) {}
    ~unique_ptr() { delete _ptr; }
    // 防拷贝:删除默认拷贝构造和赋值
    unique_ptr(unique_ptr<T>& ap) = delete;
    unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;
    
    T& operator*() {
        return *_ptr;
    }
    T* operator->() {
        return _ptr;
    }
private:
    T* _ptr;
};

五、C++11 shared_ptr(共享所有权,引用计数)

适用场景:需要拷贝 / 共享资源

int main() {
    shared_ptr<A> sp1(new A(1));
    shared_ptr<A> sp2(sp1); // 拷贝:引用计数+1
    sp1->_a = 10;          // sp2同步可见
    cout << sp2->_a << endl; // 输出10
    return 0;
}

模拟实现(引用计数核心逻辑)

// 类模板定义
template<class T>
class shared_ptr {
public:
    // 构造函数:初始化指针和引用计数(初始为1)
    shared_ptr(T* ptr = nullptr)
        : _ptr(ptr)
        , _pcount(new int(1)) {}

    // 析构函数:引用计数-1,为0时释放资源和计数对象
    ~shared_ptr() {
        if (--(*_pcount) == 0) {
            std::cout << "delete:" << _ptr << endl;
            delete _ptr;
            delete _pcount;
        }
    }

    // 重载*运算符:返回资源对象引用
    T& operator*() {
        return *_ptr;
    }

    // 重载->运算符:返回原始指针,支持成员访问
    T* operator->() {
        return _ptr;
    }

    // 拷贝构造函数:共享资源,引用计数+1
    shared_ptr(const shared_ptr<T>& sp)
        : _ptr(sp._ptr)
        , _pcount(sp._pcount) {
        ++(*_pcount);
    }

    // 赋值运算符重载:释放旧资源,获取新资源
    shared_ptr<T>& operator=(const shared_ptr<T>& sp) {
        if (_ptr == sp._ptr)  // 处理自赋值
            return *this;
        // 释放旧资源(若引用计数为0)
        if (--(*_pcount) == 0) {
            delete _ptr;
            delete _pcount;
        }
        // 接管新资源,引用计数+1
        _ptr = sp._ptr;
        _pcount = sp._pcount;
        ++(*_pcount);
        return *this;
    }

    // 获取当前引用计数(补充功能)
    int use_count() const {
        return *_pcount;
    }

    // 获取原始指针(补充功能)
    T* get() const {
        return _ptr;
    }

private:
    T* _ptr;          // 指向管理的资源
    int* _pcount;     // 指向引用计数(每个资源独立,非静态)
};
  • shared_ptr<T>& operator=(const shared_ptr<T>& sp) 中 if (--(*_pcount) == 0) 的目的:这一步的目的是在进行赋值操作时,确保旧的资源能够被正确释放。当一个 shared_ptr 对象被赋值为另一个 shared_ptr 对象时,它需要先释放当前所管理的资源(如果这是最后一个引用),然后再接管新的资源。示例代码:
    int main() {
        shared_ptr<A> sp1(new A(1));
        shared_ptr<A> sp2(new A(2));
    
        // 现在 sp1 指向新的资源,旧的资源需要被释放
        sp1 = sp2; 
    
        cout << sp1.use_count() << endl; // 输出2
        cout << sp2.use_count() << endl; // 输出2
    
        return 0;
    }

    在上述代码中,sp1 最初管理一个 A 对象,其引用计数为 1。当执行 sp1 = sp2 时,sp1 需要先释放它当前管理的资源。通过 --(*_pcount) == 0 的检查,如果引用计数变为 0,说明这是最后一个引用,就可以安全地删除 _ptr 和 _pcount

  • 2. shared_ptr类中的独立引用技术类型为什么必须是 int* _pcount 指针类型而不能是 int 和 static int 类型?

    • 不能用 int 类型的原因

      若使用 int 类型,每个 shared_ptr 对象都会有一份独立的引用计数副本,而非多个 shared_ptr 对象共享同一个引用计数。这就会致使在进行拷贝构造或者赋值操作时,引用计数无法正确更新,进而引发资源的多次释放或者资源泄漏问题。

    • 不能用 static int 类型的原因

      static int 类型的变量会被该类的所有对象共享。也就是说,所有 shared_ptr 对象都会使用同一个引用计数,这就无法对不同资源的引用计数进行独立管理。

循环引用问题:导致内存泄漏

struct Node {
    A _val;
    shared_ptr<Node> _next;
    shared_ptr<Node> _prev;
};
int main() {
    shared_ptr<Node> sp1(new Node); // sp1计数=1,sp1->_next=空
    shared_ptr<Node> sp2(new Node); // sp2计数=1,sp2->_prev=空
    sp1->_next = sp2; // 节点A的_next指向B,此时sp2的计数+1 → sp2计数=2
    sp2->_prev = sp1; // 节点B的_prev指向A,此时sp1的计数+1 → sp1计数=2

    return 0;//sp1析构:sp1计数-1 → 计数=1(未归零,不释放节点A);     sp2析构:sp2计数-1 → 计数=1(未归零,不释放节点B)。
}

 六、C++11 weak_ptr(解决循环引用,不参与计数)

定位:专门解决 shared_ptr 循环引用的辅助类,不参与资源管理(非 RAII)

修改节点结构:使用 weak_ptr 替代 shared_ptr

// 修正:用weak_ptr定义非管理型引用
struct Node {
    A _val;
    weak_ptr<Node> _next;  // weak_ptr不增加引用计数
    weak_ptr<Node> _prev;
};

int main() {
    shared_ptr<Node> sp1(new Node);  // sp1计数1
    shared_ptr<Node> sp2(new Node);  // sp2计数1
    
    cout << sp1.use_count() << endl;  // 输出1(_next是weak_ptr,不影响计数)
    cout << sp2.use_count() << endl;  // 输出1
    
    sp1->_next = sp2;  // weak_ptr赋值,sp2计数不变(仍为1)
    sp2->_prev = sp1;  // weak_ptr赋值,sp1计数不变(仍为1)
    
    cout << sp1.use_count() << endl;  // 输出1(_prev是weak_ptr,未增加计数)
    cout << sp2.use_count() << endl;  // 输出1
    
    // 结论:无循环引用,main结束时sp1/sp2计数降为0,资源正常释放
    return 0;
}
  • 核心特性
    1. 不增加引用计数:仅弱引用资源,不影响 shared_ptr 的计数逻辑
    2. 观察资源生命周期:可判断资源是否存活,或临时获取 shared_ptr
    3. 配合 shared_ptr 使用:不能单独管理资源,必须依赖 shared_ptr 存在

weak_ptr 模拟实现(核心逻辑)

template<class T>
class weak_ptr {
public:
    weak_ptr() :_ptr(nullptr) {}                          // 空构造
    weak_ptr(const shared_ptr<T>& sp) :_ptr(sp.get()) {}  // 从shared_ptr获取原始指针
    weak_ptr<T>& operator=(const shared_ptr<T>& sp) {      // 赋值时获取原始指针
        _ptr = sp.get();
        return *this;
    }
    T& operator*() { return *_ptr; }                       // 像指针一样解引用
    T* operator->() { return _ptr; }                       // 像指针一样访问成员
private:
    T* _ptr;  // 存储shared_ptr管理的原始指针(弱引用,不控制生命周期)
};

七、定制删除器

1.定制删除器的需求场景

默认删除器的局限性(问题引入)

class A {
public:
    A(int a = 0) : _a(a) {
        cout << "A(int a = 0)" << endl;
    }
    ~A() {
        cout << this << " ~A()" << endl;
    }
    int _a;
};

int main() {
    // 场景1:管理动态数组(默认delete导致未定义行为)
    shared_ptr<A> sp1(new A[10]); // ❌ 错误!默认用delete释放数组,仅释放首元素,导致内存泄漏

    // 场景2:管理非new分配的内存(如malloc)
    shared_ptr<A> sp2((A*)malloc(sizeof(A))); // ❌ 错误!delete与malloc不匹配,导致程序崩溃

    return 0;
}

核心问题

  • shared_ptr 默认使用 delete ptr 释放资源
  • 对 数组 需用 delete[],对 malloc 内存 需用 free,对 特殊资源(如文件句柄)需专用释放逻辑

2.定制删除器的实现方式

仿函数定制删除器(适用于数组释放)

template<class T>
struct DeleteArray {
    void operator()(T* ptr) { // 重载()运算符,定义释放逻辑
        delete[] ptr; // 正确释放数组内存
    }
};

int main() {
    // 传入仿函数删除器,解决数组释放问题
    shared_ptr<A> sp1(new A[10], DeleteArray<A>()); 
    // 传入Lambda表达式,解决malloc内存释放问题
    shared_ptr<A> sp2((A*)malloc(sizeof(A)), [](A* ptr){free(ptr);}); 
    // 自定义文件句柄释放(避免fclose被遗漏)
    shared_ptr<FILE> sp3(fopen("Test.cpp", "r"), 
        [](FILE* ptr) { cout << "fclose:" << ptr << endl; fclose(ptr); });
    return 0;
}

关键点

  • shared_ptr允许通过第二个参数传入删除器函数对象(仿函数或 Lambda),替代默认的delete
  • Lambda 表达式可直接捕获上下文变量,简化自定义释放逻辑(如文件句柄释放时打印日志)。

 3.自定义 shared_ptr 类:实现定制删除器的底层原理

带删除器的 shared_ptr 类模板(关键修改点)

#include <functional> // 引入function包装器

template<class T>
class shared_ptr {
public:
    // 构造函数(默认删除器:lambda表达式[](T* ptr){delete ptr;})
    shared_ptr(T* ptr = nullptr)
        : _ptr(ptr), _pcount(new int(1)), _del([](T* ptr) { delete ptr; }) {} 

    // 带定制删除器的构造函数(通过模板参数D接收任意可调用对象)
    template<class D>
    shared_ptr(T* ptr, D del)
        : _ptr(ptr), _pcount(new int(1)), _del(del) {} 

    ~shared_ptr() {
        if (--(*_pcount) == 0) { // 引用计数为0时释放资源
            cout << "delete:" << _ptr << endl;
            _del(_ptr); // 调用存储的删除器(关键!替代默认delete)
            delete _pcount;
        }
    }

    // ...其他成员函数(拷贝构造、赋值运算符等)...//

private:
    T* _ptr; // 管理的原始指针
    int* _pcount; // 引用计数
    function<void(T*)> _del = [](T* ptr) { delete ptr; }; // 核心:用function包装器存储任意删除器,同时也要确保即使不传入删除器,也会使用默认的delete释放单个对象。
};
 为什么需要 function 包装器?
  • 问题:Lambda 表达式和仿函数是不同类型的可调用对象,直接用普通成员变量无法统一存储。
  • 解决方案std::function是一个类型擦除的包装器,可以统一存储任意可调用对象(函数指针、仿函数、Lambda),只要其签名符合void(T*)
    • 例如:DeleteArray<A>()(仿函数)和[](A* ptr){free(ptr);}(Lambda)都会被function转换为统一类型。

    4.对比 unique_ptr 的定制删除器 

    unique_ptr 的删除器类型定义

    template<class T, class D = default_delete<T>> class unique_ptr;
    • 特点
      • 删除器类型D是模板参数,直接影响unique_ptr的类型(如unique_ptr<A, DeleteArray<A>>)。
      • 不支持引用计数,删除器直接存储在unique_ptr对象内,而非共享。
    • 优势:无需function包装器,直接通过模板参数支持定制删除器,效率更高。
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值