C++异常与智能指针

异常

引言

C++异常是一种处理程序运行时错误的方式。当程序运行中出现意外情况(比如数组越界、除以零、文件打开失败等),我们可以通过抛出异常来中断正常的流程,然后由专门的异常处理代码来捕获并处理这个异常,从而避免程序崩溃并提供错误恢复的机会。

异常的处理主要基于三个关键字:trycatchthrow

  • try:定义可能抛出异常的代码块
  • throw:抛出异常对象
  • catch:捕获异常对象

基本语法

cpp 复制代码
try {
    // 可能抛出异常的代码
    if (错误条件) 
        throw 异常对象; // 抛出一个异常,可以是内置类型/自定义类型
} 
catch (异常类型1 参数)
{
    // 处理异常类型1的代码
} 
catch (异常类型2 参数) 
{
    // 处理异常类型2的代码
} 
catch (...) // ...表示可以捕获任何类型的异常
{ 
    // 处理该异常的代码
}

举个例子

cpp 复制代码
void test01()
{
    try{
        int x, y;
        cout << "请输入被除数与除数" << endl;
        cin >> x >> y;	// 输入 1 0
        if(y == 0)
            throw "除数为0!";     // 抛出const char*类型的异常
        else
            cout << x / y << endl;
    }
    catch(const char* err_message)
    {
        cout << err_message << endl;
    }
}

运行结果

注意事项

1.抛出异常对象后,该对象的类型决定应该匹配哪个catch块,该过程不会有隐式类型转换

cpp 复制代码
void test01()
{
    try{
        int x, y;
        cout << "请输入被除数与除数" << endl;
        cin >> x >> y;	// 输入 1 0
        if(y == 0)
            throw "除数为0!";     // 抛出const char*类型的异常
        else
            cout << x / y << endl;
    }
    catch(const string& err_message)    // 该catch块接收string类型
    {
        cout << err_message << endl;
    }
    catch(...)      // 三个点表示可以捕获任何类型的异常
    {
        cout << "未知错误" << endl;
    }
}

运行结果

const char*类型的对象不会匹配到const string&的catch块。把上述代码改为:

cpp 复制代码
void test01()
{
    try{
        int x, y;
        cout << "请输入被除数与除数" << endl;
        cin >> x >> y;	// 输入 1 0
        if(y == 0)
            throw string("除数为0!");     // 抛出string类型的异常
        else
            cout << x / y << endl;
    }
    catch(const string& err_message)    // 该catch块接收string类型
    {
        cout << err_message << endl;
    }
    catch(...)      // 三个点表示可以捕获任何类型的异常
    {
        cout << "未知错误" << endl;
    }
}

再次运行,运行结果:

再例如

cpp 复制代码
void test01()
{
    try{
        int x, y;
        cout << "请输入被除数与除数" << endl;
        cin >> x >> y;	// 输入 1 0
        if(y == 0)
            throw 0;     // 抛出int类型
        else
            cout << x / y << endl;
    }
    catch(size_t err_message)    // 该catch块接收size_t类型
    {
        cout << "catch(size_t err_message)" << endl;
    }
    catch(...)      // 三个点表示可以捕获任何类型的异常
    {
        cout << "未知错误" << endl;
    }
}

运行结果

int类型的异常,不会匹配到size_t类型的catch

注:抛出子类 对象,可以被父类捕获。(实践中常用)

cpp 复制代码
class A
{};

class B : public A
{};

void test01()
{
    try{
        int x, y;
        cout << "请输入被除数与除数" << endl;
        cin >> x >> y;	// 输入 1 0
        if(y == 0)
            throw B();     // 抛出子类对象
        else
            cout << x / y << endl;
    }
    catch(const A& err_message)    // 该catch块用父类接收
    {
        cout << "catch(const A& err_message)" << endl;
    }
    catch(...)      // 三个点表示可以捕获任何类型的异常
    {
        cout << "未知错误" << endl;
    }
}

运行结果

2.抛出异常在当前块未找到对应的catch,则会沿着函数调用链向上找第一个匹配的catch

cpp 复制代码
void test01()
{
    try{
        int x, y;
        cout << "请输入被除数与除数" << endl;
        cin >> x >> y;	// 输入 1 0
        if(y == 0)
            throw "除数为0!";     // 抛出const char*类型的异常
        else
            cout << x / y << endl;
    }
    catch(const string& err_message)    // 接收string类型
    {
        cout << "test01(): catch(size_t err_message)" << endl;
    }
}
int main()
{
    try{
        test01();
    }
    catch(...)
    {
        cout << "main(): catch(...)" << endl;
    }
    return 0;
}

运行结果

如果到了main函数也没有找到对应的catch,程序会直接终止

3.异常的重新抛出

当前catch捕获了异常,但无法完全处理该异常,此时在catch代码块中,可以通过throw关键字将异常重新抛出,交给上层处理。

cpp 复制代码
void test01()
{
    try{
        int x, y;
        cout << "请输入被除数与除数" << endl;
        cin >> x >> y;	// 输入 1 0
        if(y == 0)
            throw string("除数为0!");     // 抛出const char*类型的异常
        else
            cout << x / y << endl;
    }
    catch(const string& err_message)    // 该catch块接收string类型
    {
        cout << "test01(): catch(const string& err_message)" << endl;
        throw;  // 重新抛出异常
    }
}
int main()
{
    try{
        test01();
    }
    catch(...)
    {
        cout << "main(): catch(...)" << endl;
    }
    return 0;
}

运行结果

4.抛出异常后,会直接跳到对应的catch块,throw后面的代码不会执行

cpp 复制代码
void test01()
{
    try{
        int x, y;
        cout << "请输入被除数与除数" << endl;
        cin >> x >> y;
        if(y == 0)
            throw "被除数为0!";
        else
            cout << x / y << endl;

        cout << "xxxxxxxxxxxxxxx" << endl;  // 抛出异常后,后面的代码就不会执行
    }
    catch(const char* err_message)
    {
        cout << err_message << endl;
    }
}

运行结果

这就可能存在内存泄露的风险,例如:

cpp 复制代码
void test01()
{
    try{
        cout << "申请空间" << endl;
        int* arr = new int[10];
        
        int x, y;
        cout << "请输入被除数与除数" << endl;
        cin >> x >> y;	// 输入 1 0
        if(y == 0)
            throw "被除数为0!";
        else
            cout << x / y << endl;

        cout << "释放空间" << endl;
        delete[] arr;
    }
    catch(const char* err_message)
    {
        cout << err_message << endl;
    }
}

运行结果

针对该问题,C++的提供了智能指针(后面会讲)

异常规范

C++11新增关键字noexcept:如果确定当前函数不会抛异常,可以在其后面加上noexcept

cpp 复制代码
// 承诺:这个函数绝对不会抛出异常
void func() noexcept
{
    // ... 
}

如果函数后面加上了noexcept,但函数体抛出了异常,程序会直接终止

经验之谈:构造函数析构函数最好不要抛出异常

智能指针

回顾之前的代码

cpp 复制代码
void test01()
{
    try{
        cout << "申请空间" << endl;
        int* arr = new int[10];
        
        int x, y;
        cout << "请输入被除数与除数" << endl;
        cin >> x >> y;	// 输入 1 0
        if(y == 0)
            throw "被除数为0!";
        else
            cout << x / y << endl;

        cout << "释放空间" << endl;
        delete[] arr;
    }
    catch(const char* err_message)
    {
        cout << err_message << endl;
    }
}

该代码存在内存泄漏的风险。但是再看一段代码:

cpp 复制代码
class A
{
public:
    A() { cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }
};

void test01()
{
    try{
        A a;    // 抛出异常后,a的生命周期也随之结束,会调用其析构
        int x, y;
        cout << "请输入被除数与除数" << endl;
        cin >> x >> y;	// 输入 1 0
        if(y == 0)
            throw "被除数为0!";
        else
            cout << x / y << endl;
    }
    catch(const char* err_message)
    {
        cout << err_message << endl;
    }
}

运行结果

受此启发,我们可以把指针封装成一个类

cpp 复制代码
template<class T>
class smartPtr
{
    T* _p;
public:
    smartPtr(T* p)
        :_p(p)
    {}
    ~smartPtr()
    {
        cout << "delete _p" << endl;
        delete _p;
    }
};

// 特化数组版本
template<class T>
class smartPtr<T[]>		// 例如 smartPtr<int[]> 会走这里,T会被推导为int
{
    T* _p;
public:
    smartPtr(T* p)
        :_p(p)
    {}
    ~smartPtr()
    {
        cout << "delete[] _p" << endl;
        delete[] _p;
    }
};


void test01()
{
    try{
        smartPtr<int[]> arr(new int[10]);
        int x, y;
        cout << "请输入被除数与除数" << endl;
        cin >> x >> y;	// 输入 1 0
        if(y == 0)
            throw "被除数为0!";
        else
            cout << x / y << endl;
    }
    catch(const char* err_message)
    {
        cout << err_message << endl;
    }
}

运行结果

这样就避免了内存泄漏。

智能指针就是把原生指针交给对象管理,利用对象的生命周期 来控制资源。这正是RAII设计模式的核心思想------资源的获取和释放与对象的生命周期绑定。(资源的获取通常发生在对象的构造函数中,而资源的释放则发生在对象的析构函数中)

再加入运算符重载让smartPtr能像原生指针那样使用

cpp 复制代码
template<class T>
class smartPtr
{
    T* _p;
public:
    smartPtr(T* p)
        :_p(p)
    {}
    ~smartPtr()
    {
        delete _p;
        cout << "delete _p" << endl;
    }
    // 加入运算符重载
    T& operator* () { return *_p; }
    T* operator-> () { return _p; }
};

// 特化数组版本
template<class T>
class smartPtr<T[]>
{
    T* _p;
public:
    smartPtr(T* p)
        :_p(p)
    {}
    ~smartPtr()
    {
        delete[] _p;
        cout << "delete[] _p" << endl;
    }
    // 加入运算符重载
    T& operator* () { return *_p; }
    T* operator-> () { return _p; }
    T& operator[] (int n) { return *(_p + n); }
};

测试

cpp 复制代码
struct Date
{
    int _y, _m, _d;
    Date(int y = 2025, int m = 11, int d = 30)
        :_y(y), _m(m), _d(d)
    {}
    ~Date() { cout << "~Date()" << endl; }
};

void test02()
{
    smartPtr<int> a(new int);
    *a = 20;                // *a 相当于 a.operator*()
    cout << *a << endl;
    smartPtr<int[]> a1(new int[5]);
    for(int i = 0; i < 5; i++) 
        a1[i] = i;          // a1[i] 相当于 a1.operator[](i)
    for(int i = 0; i < 5; i++) 
        cout << a1[i] << ' ';
    cout << endl;

    smartPtr<Date> a2(new Date);
    a2->_y = 2026;          // a2--->_y 相当于 a2.operator->()->_y。 为了可读性,省略了一个箭头
    cout << a2->_y << ' ' << a2->_m << ' ' << a2->_d << endl;
}

运行结果

这样就不存在内存泄露了,还能像原生指针那样使用,岂不美哉?

但是上述smartPtr还是有些问题的,例如

cpp 复制代码
void test03()
{
    smartPtr<int> a(new int(20));
    smartPtr<int> a1(a);    // a1拷贝a
}

上述代码就会崩溃,原因如下:

为了解决此问题,有两种方法

法一:简单粗暴点,直接把拷贝构造 给禁用不就行了嘛!(当然了,赋值重载 也得禁掉)

法二:用引用计数

下面先用法一实现。(文章结尾的模拟实现shared_ptr会用到法二)

cpp 复制代码
template<class T>
class smartPtr
{
    // ...省略其他代码
    // 禁用拷贝构造与赋值重载
    smartPtr(const smartPtr& sp) = delete;
    smartPtr& operator= (const smartPtr& sp) = delete;
};

// 特化数组版本
template<class T>
class smartPtr<T[]>
{
    T* _p;
public:
    // ...省略其他代码
    // 禁用拷贝构造与赋值重载
    smartPtr(const smartPtr& sp) = delete;
    smartPtr& operator= (const smartPtr& sp) = delete;
};

这样,一个简易的智能指针已经实现完毕。

(上文的smartPtr其实就简单模拟C++库里面的unique_ptr

unique_ptr

unique_ptr用于独占所有权 的资源管理(禁止拷贝)。对象生命周期结束时自动释放内存,防止内存泄漏。

unique_ptr官方文档

下面介绍一些常用的

构造

cpp 复制代码
struct Date
{
    int _y, _m, _d;
    Date(int y = 2025, int m = 11, int d = 30)
        :_y(y), _m(m), _d(d)
    {}
    ~Date() { cout << "~Date()" << endl; } // 方便观察是否释放资源
};
下文中所用到的Date类,都是上面这个
cpp 复制代码
void test04()
{
	// 头文件:#include<memory>
    unique_ptr<Date> up1;           // 默认构造,内部指针为nullptr;
    unique_ptr<Date> up2(new Date(2025, 12, 1));        // 直接构造
    // unique_ptr<Date> up6 = new Date(2025, 12, 1);    // err 不可隐式类型转换
    unique_ptr<Date> up3(new Date(2026, 12, 12));       // 直接构造

    // unique_ptr<Date> up4(up2);  // err 禁止拷贝构造
    unique_ptr<Date> up4(move(up2));  // 支持移动构造。此时up2变为空
    unique_ptr<Date> up5;
    // up5 = up3;       // err 禁止赋值重载
    up5 = move(up3);    // 支持移动赋值。此时up3变为空
    
    // 数组版本,需加上[],否则可能会崩溃
    unique_ptr<Date[]> up6(new Date[3]);
    
    // unique_ptr的对象生命周期结束后,自动delete
}

运行结果

C++14起,更推荐用make_unique构造,更安全高效

cpp 复制代码
unique_ptr<Date> up = make_unique<Date>(2025, 12, 1);
// 等价于:unique_ptr<Date> up(new Date(2025, 12, 1));

unique_ptr<Date[]> up1 = make_unique<Date[]>(3);
// 等价于:unique_ptr<Date[]> up1(new Date[3]);

访问成员

unique_ptr的对象可以像原生指针那样使用

cpp 复制代码
void test06()
{
    unique_ptr<Date> up1(new Date); 
    up1->_y = 2026;
    (*up1)._m = 2;

    if(up1)		
        cout << "不为空" << endl;
    if(up1 != nullptr)
        cout << "不为空" << endl;

    // get()返回对应的原生指针
    Date* p = up1.get();

    // 数组版本还提供了[]访问
    unique_ptr<Date[]> up2(new Date[3]);
    up2[1]._y = 2027, up2[1]._m = 1, up2[1]._d = 1; 
}

release与reset

release

cpp 复制代码
pointer release() noexcept;
返回对象的原生指针,并将对象的原生指针重置为nullptr。该过程并不会delete原来的原生指针
cpp 复制代码
void test07()
{
    unique_ptr<Date> up1(new Date);
    Date* p = up1.release();    // 返回up1内的原生指针,并把up1内的原生指针置为空。此过程up1并不会delete原生指针
    delete p; // 需要手动释放!
}

reset

cpp 复制代码
void reset (pointer p = pointer()) noexcept;
释放旧指针指向的资源,再把对象的原生指针重置为p
cpp 复制代码
void test07()
{
    unique_ptr<Date> up2(new Date);
    up2.reset(new Date(2000, 1, 1));	// 先释放旧资源,再把指针指向新资源
}

自定义删除器

cpp 复制代码
struct FileDeleter
{
    void operator()(FILE* pf) const
    {
        fclose(pf);
        cout << "fclose(pf);" << endl;
    }
};

void test05()
{
	// C语言的文件指针,需要自己写删除器
	// 仿函数写法
    unique_ptr<FILE, FileDeleter> pr(fopen("test.cpp", "r")); 

    auto fdel = [](FILE* pf)
    {
        fclose(pf);
        cout << "fclose(pf);" << endl;
    };
    // lambda写法。需把删除器的对象传过去
    unique_ptr<FILE, decltype(fdel)> pr1(fopen("test.cpp", "r"), fdel);  
}

注意事项

  1. 不要把一个原生指针给多个智能指针管理

    cpp 复制代码
    Date* p = new Date;
    unique_ptr<Date> up1(p);	// p给up1管理
    unique_ptr<Date> up2(p);	// p又给up2管理, 会释放两次!!
  2. 记得要用release的返回值,release并不会释放原来的资源

  3. 禁止释放get()返回的原生指针;也不要把get()返回的原生指针交给另一个智能指针管理

shared_ptr

unique_ptr禁止拷贝,但有时候还是会用到拷贝,这就需要用shared_ptr了。(但是shared_ptrunique_ptr好像不是同一个人写的,两者用法会有些差异。)

shared_ptr核心是共享所有权 ------多个shared_ptr可以指向同一个对象,通过引用计数跟踪"使用者数量"。当拷贝/赋值时,引用计数+1;析构时引用计数-1,若析构后计数变为0,再delete

shared_ptr用法官方文档

下面介绍一些常用的

构造

cpp 复制代码
void test_shared_ptr01()
{
	// 头文件 #include<memory>
    shared_ptr<int> sp;                 // 默认构造,内部指针为nullptr
    shared_ptr<int> sp1(new int(1));    // 直接构造。指向值为1的int,计数为1

    shared_ptr<int> sp2 = make_shared<int>(20);     // 指向值为20的int,计数为1
    //  C++11就支持了make_shared,而之前的make_unique到了C++14才支持
    // 更推荐用make_shared创建对象,可以减少内存碎片

    shared_ptr<int> sp3(sp2);   // 拷贝构造。也指向值为20的int,计数=2
    
    {
        shared_ptr<int> sp4(sp3);   // 拷贝sp3,也指向值为20的int,计数=3
        // sp4生命周期结束,计数-1
    }
    sp = sp3;   // 赋值重载。也指向值为20的int,计数=3
    
    // 数组版本。指向数组的shared_ptr建议手写删除器
    shared_ptr<int[]> sp_arr(new int[3]);  // C++17支持。而unique_ptr的数组版本C++11就支持了
}

访问成员

shared_ptr的对象可以像原生指针那样使用

cpp 复制代码
void test_shared_ptr02()
{
    auto sp1 = make_shared<pair<int,int>>(1, 2);
    sp1->first = 888;
    (*sp1).second = 999;

    if(sp1) 
        cout << "不为空" << endl;
    if(sp1 != nullptr) 
        cout << "不为空" << endl;
        
    // get()返回对应的原生指针
    pair<int,int>* pp = sp1.get();	
    
    // use_count()返回引用计数
    int cnt = sp1.use_count();
}

reset

cpp 复制代码
void test_shared_ptr03()
{
    auto sp1 = make_shared<Date>(); // 指向Date(2025,11,30), 计数=1
    sp1.reset();    // 释放旧资源,sp1重置为空。【这里的释放指的是:计数-1,当计数变为0时,才delete】

    auto sp2 = make_shared<Date>(2000, 1, 1);   // 指向Date(2000,1,1),计数=1
    auto sp3(sp2);          // 指向Date(2000,1,1),计数=2
    sp2.reset(new Date(2000, 12, 31));  // 释放旧资源,sp2指向Date(2000, 12, 31),计数=1
}

自定义删除器

cpp 复制代码
struct Date
{
    int _y, _m, _d;
    Date(int y = 2025, int m = 11, int d = 30)
        :_y(y), _m(m), _d(d)
    {}
    ~Date() { cout << "~Date()" << endl; }
};

template<class T>
struct ArrDeleter
{
    void operator()(T* p) const
    {
        delete[] p;
        cout << "delete[] p;" << endl;
    }
};
struct FileDeleter
{
    void operator()(FILE* pf) const
    {
        fclose(pf);
        cout << "fclose(pf);" << endl;
    }
};

void test_shared_ptr04()
{
    // 注意share_ptr自定义删除器不是在模板里写,而是把可调用对象传给构造函数的参数。
    shared_ptr<Date> sp(new Date[3], ArrDeleter<Date>());

    // lambda写法
    auto Date_arr_deleter = [](Date* d) 
    { 
        delete[] d; 
        cout << "delete[] d;" << endl;
    };
    shared_ptr<Date> sp1(new Date[3], Date_arr_deleter);

    shared_ptr<FILE> sp2(fopen("test.cpp", "r"), FileDeleter());
}

shared_ptr循环引用问题

循环引用:指两个或多个对象互相持有对方的引用,形成一个环,导致它们无法被释放。

简单示例:

cpp 复制代码
struct ListNode
{
    int _val;
    shared_ptr<ListNode> _next;	// 这里用shared_ptr
    shared_ptr<ListNode> _prev;
    ListNode()
        :_val(0)
    { cout << "ListNode() " << endl; }
    
    ~ListNode() { cout << "~ListNode() " << endl; }
};

void test_shared_ptr05()
{
    shared_ptr<ListNode> n1(new ListNode);
    shared_ptr<ListNode> n2(new ListNode);
    n1->_next = n2;
    n2->_prev = n1;
}

运行结果

并没有调用析构,内存泄漏了!原因如下



应对该情况,C++引入了weak_ptrweak_ptr专为配合shared_ptr设计,核心特点是不拥有对象所有权(不增加引用计数 ),仅用于观察对象是否存在,不会阻止对象被释放。

将上述代码改为

cpp 复制代码
struct ListNode
{
    int _val;
    weak_ptr<ListNode> _next;	// 这里用weak_ptr
    weak_ptr<ListNode> _prev;
    ListNode()
        :_val(0)
    { cout << "ListNode() " << endl; }
    
    ~ListNode() { cout << "~ListNode() " << endl; }
};

void test_shared_ptr05()
{
    shared_ptr<ListNode> n1(new ListNode);
    shared_ptr<ListNode> n2(new ListNode);
    n1->_next = n2;
    n2->_prev = n1;
}

再补充一下weak_ptr基本用法

cpp 复制代码
void test01()
{
    // 1.构造
    shared_ptr<int> sp(new int(888));
    weak_ptr<int> wp(sp);   // weak_ptr支持用shared_ptr构造,不会改变shared_ptr的引用计数
    
    cout << sp.use_count() << endl;  // 输出 1
    // 也可以通过weak_ptr查看引用计数
    cout << wp.use_count() << endl;  // 输出 1
    
    // *wp = 999;  // err weak_ptr并不支持*与->访问对象。
    // 若想访问,需用lock()返回shared_ptr再去访问
    shared_ptr<int> sp1 = wp.lock();
    cout << *sp1 << endl;
}

weak_ptrexpired()函数:检查指向的对象是否已销毁。已销毁返回true,否则返回false

cpp 复制代码
void test02()
{
    weak_ptr<int> wp;

    {
        auto sp = make_shared<int>(888);
        wp = sp;        // wp监视sp指向的内容

        // 此时sp指向的内容还未销毁
        cout << wp.expired() << endl;   // 输出: 0 
    }

    // 出作用域后,sp自动释放指向的内容
    cout << wp.expired() << endl;   // 输出: 1
}

简单模拟实现shared_ptr

每个指针指向的对象都绑定一个计数器

成员变量

cpp 复制代码
namespace Wang
{

template<class T>
class shared_ptr
{
    T* _p;      // 指向动态开辟的对象
    int* _pcnt; // 指向引用计数器

public:
	// 构造域析构
    shared_ptr(T* p = nullptr)
        :_p(p)
        ,_pcnt(new int(1))
    {}
    ~shared_ptr() { clear(); }

    void clear()
    {
        (*_pcnt)--;
        if((*_pcnt) == 0)
        {
            delete _p;
            delete _pcnt;
            _p = nullptr;
            _pcnt = nullptr;
        }
    }
    
    // 拷贝构造与赋值重载
    shared_ptr(const shared_ptr& sp)
        :_p(sp._p)
        ,_pcnt(sp._pcnt)
    {
        (*_pcnt)++; // 计数器+1
    }

    void swap(shared_ptr& sp)
    {
        std::swap(_p, sp._p);
        std::swap(_pcnt, sp._pcnt);
    }

    shared_ptr& operator= (const shared_ptr& sp)
    {
        // 这里不写成this == &sp, 因为_p指向同一块地址,我们就认为这两者相同
        if(_p == sp._p) return *this;
        shared_ptr tmp(sp);
        swap(tmp);
        return *this;
        // tmp出作用域会析构
    }
    
    T* get() { return _p; }
    int use_count() { return *_pcnt; }
    
    // 添加运算符重载让其支持像原生指针那样使用
    T& operator*() { return *_p; }
    T* operator->() { return _p; }
};

}// namespace Wang

自定义删除器

新增成员变量_del作为删除器,用function包装

cpp 复制代码
template<class T>
class shared_ptr
{
    T* _p;
    int* _pcnt;
    function<void(T*)> _del;    // 删除器
public:
    shared_ptr(T* p = nullptr)
        :_p(p)
        ,_pcnt(new int(1))
        ,_del([](T* p) { delete p; }) // 给默认值
    {}

    template<class D>
    shared_ptr(T* p, D del)
        :_p(p)
        ,_pcnt(new int(1))
        ,_del(del)	// 自定义删除器
    {}
    ~shared_ptr() { clear(); }

    void clear()
    {
        (*_pcnt)--;
        if((*_pcnt) == 0)
        {
            // delete _p;
            _del(_p);	// 用_del删除
            delete _pcnt;
            _p = nullptr;
            _pcnt = nullptr;
        }
    }

    shared_ptr(const shared_ptr& sp)
        :_p(sp._p)
        ,_pcnt(sp._pcnt)
        ,_del(sp._del)
    {
        (*_pcnt)++; // 计数器+1
    }

    void swap(shared_ptr& sp)
    {
        std::swap(_p, sp._p);
        std::swap(_pcnt, sp._pcnt);
        std::swap(_del, sp._del);
    }

    shared_ptr& operator= (const shared_ptr& sp)
    {
        // 这里不写成this == &sp, 因为_p指向同一块地址,我们就认为这两者相同
        if(_p == sp._p) return *this;
        shared_ptr tmp(sp);
        swap(tmp);
        return *this;
        // tmp出作用域会析构
    }

    T* get() { return _p; }
    int use_count() { return *_pcnt; }
    T& operator*() { return *_p; }
    T* operator->() { return _p; }
};
相关推荐
m0_488913011 小时前
Deep Research技术全解析:从Reasoning到Research with Reasoning的AI进化之路(值得收藏)
开发语言·人工智能·机器学习·大模型·ai大模型·大模型学习
烤麻辣烫1 小时前
黑马程序员苍穹外卖(新手)DAY8
java·开发语言·学习·spring·intellij-idea
就叫飞六吧1 小时前
Java 中编译一个 java 源文件产生多个 .class 文件原因
java·开发语言
IMPYLH1 小时前
Lua 的 rawset 函数
开发语言·笔记·单元测试·lua
python零基础入门小白1 小时前
2025年大模型面试通关秘籍!大厂高频LLMs真题全解析,一文掌握,助你轻松斩获心仪offer!
开发语言·人工智能·语言模型·架构·langchain·大模型教程·大模型面试
chenyuhao20241 小时前
MySQL事务
开发语言·数据库·c++·后端·mysql
爪哇部落算法小助手1 小时前
每日两题day59
数据结构·c++·算法
g***78911 小时前
Java语法进阶
java·开发语言·jvm
D_evil__2 小时前
[C++高频精进] 现代C++特性:右值引用和移动语义
c++