C++面向对象(OOP)编程-智能指针

本文旨在通过比较简单的语言理解智能指针,区分三种智能指针shared_ptr、unique_ptr、weak_ptr。同时解决shared_ptr循环引用的问题。

目录

1 为什么引入智能指针   

2 智能指针的定义

3 智能指针的分类

3.1 shared_ptr

3.2 unique_ptr

3.3 weak_ptr

4 智能指针的使用

4.1 auto_ptr 

4.2 unique_ptr

4.3 shared_ptr

4.4 weak_ptr

5 简单对比4种智能指针

6 常见面试题

6.1 智能指针的分类?以及各自的特点

6.2 unique_ptr 能否被另一个 unique_ptr 拷贝呢?

6.3 unique_ptr 和 shared_ptr 的区别

6.4 unique_ptr = unique_ptr和shared_ptr=shared_ptr这两个操作有什么后果呢?

6.5 shared_ptr 的移动赋值时发生了什么事情

6.6 shared_ptr 什么时候会被释放掉,就是什么时候引用次数为0?

6.7 shared_ptr 你是怎么实现的

6.8 shared_ptr是不是线程安全

6.9 sharedPtr在64位操作系统下大小有多大

6.10 shared_ptr和weak_ptr之间是什么关系?

6.11 为什么推荐用make_shared创建指针

6.13 weak_ptr的原理?weak_ptr的使用场景

6.14 weak_ptr如何检测指针是否被销毁

6.15 如何将weak_ptr转换为shared_ptr


1 为什么引入智能指针   

        为了解决内存泄露等问题,使用RALL机制,即资源获取即初始化,使用构造函数进行资源的初始化,使用析构函数进行资源的释放。这种情况下仍然会有内存泄漏问题,由此引入智能指针。

一个例子:

#include 

using namespace std;

class Object{
    public:
        Object()
        {
            cout << "Create Object!" << endl;
        }
        ~Object()
        {
            cout << "Free Object!" << endl;
        }
        void run()
        {
            cout << "The test is to test smart pointer!" << endl;
        }
        void throw_test()
        {
            throw -1;
        }
};

int main(){

    try{
        {
            Object* pObj = new Object();
            // pObj->run();
            pObj->throw_test();
            cout << "test1" << endl;
            delete pObj;
            cout << "test2" << endl;
        }
    }catch(...){
        cout << " 捕捉到异常!" << endl;

    }
    return 0;
}

        运行结果:

分析上述代码:

        throw_test() 被调用后,抛出异常,此时下面的不能被执行,因此delete没有生效,会造成内存泄露,如果对于类似的忘记进行delete也会造成内存的泄露。

对此,C++C++之父Bjarne Stroustrup提出了 RAII(Resource Acquisition Is Initialization) 机制,

翻译过来就是“资源获取就是初始化”

RAII要求由对象的构造函数完成资源的分配,同时由析构函数,完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现资源泄露问题。而 C++智能指针 正是RAII的实践。

构造函数与析构函数的名字需要与类名一致。

最早在c++98引入auto_ptr智能指针,但不太完善,在c++11弃用,c++17彻底删除。所以现在智能指针一般指的是C++11后引入的智能指针。

        几种智能指针:

  • auto_ptr:已经不用了
  • unique_ptr:独占式指针,同一时刻只能有一个指针指向同一个对象
  • shared_ptr:共享式指针,同一时刻可以有多个指针指向同一个对象
  • weak_ptr:用来解决shared_ptr相互引用导致的死锁问题

    版本:  

      c++ 版本: c++98 c++03 c++11 c++14 c++17 c++20

        c 版本: c89 c90 c95 c99 c11 c17

2 智能指针的定义

        智能指针是C++11引入的类模板,用于管理资源,行为类似于指针,但不需要手动申请、释放资源,所以称为智能指针。

        所以智能指针的本质是类模板。

3 智能指针的分类

3.1 shared_ptr

        shared_ptr使用了引用计数(use count)技术,当复制个shared_ptr对象时,被管理的资源并没有被复制,而是增加了引用计数。当析构一个shared_ptr对象时,也不会直接释放被管理的的资源,而是将引用计数减一。当引用计数为0时,才会真正的释放资源。shared_ptr可以方便的共享资源而不必创建多个资源。

C++面向对象(OOP)编程-智能指针_第1张图片

3.2 unique_ptr

        unique_ptr则不同。unique_ptr独占资源,不能拷贝,只能移动。移动过后的unique_ptr实例不再占有资源。当unique_ptr被析构时,会释放所持有的资源。

C++面向对象(OOP)编程-智能指针_第2张图片

3.3 weak_ptr

        weak_ptr可以解决shared_ptr所持有的资源循环引用问题。weak_ptr在指向shared_ptr时,并不会增加shared_ptr的引用计数。所以weak_ptr并不知道shared_ptr所持有的资源是否已经被释放。这就要求在使用weak_ptr获取shared_ptr时需要判断shared_ptr是否有效。

struct A;
struct B{
    std::shared_ptr boo;
};
struct A{
    std::shared_ptr foo;
};

        A中有一个智能指针指向B,而B中也有一根智能指针指向A,这就是循环引用,我们可以使用weak_ptr来解决这个问题。

A boo;
auto foo = boo.foo.lock();
if(foo)
{
    //这里通过获取到了foo,可以使用
}else
{
    //这里没有获取到,不能使用
}

4 智能指针的使用

4.1 auto_ptr 

        std::auto_ptr 在c++11 以及以上版本被弃用,只能在c++11以下,包括c++03 c++98 等可以使用。

        smart_ptr pobj(new type(value)); 只能去这样定义,因为其构造函数阻止了其隐式转换。

        智能指针本质上是一个对象,用来托管对象的地址,可以通过pobj.get()来获取其托管对象的地址

        ptr.get() 与*ptr 都是取其对象的值

        auto_ptr 在所有权被转移后,如果访问原来的智能指针,造成程序的错误,因此逐渐被弃用

        此外auto_ptr对象并不能作为STL容器的元素,因为C++的STL容器对于容器元素类型的要求是有值语义,即可以赋值和复制,

        auto_ptr在赋值和复制时都进行了特殊操作;也不能声明为数组,因为auto_ptr无法识别数组,其内部析构时是用的delete而非delete [],它只会析构一次,导致内存泄漏:

        auto_ptr意义不明确,使用浅拷贝方式时,两个对象拥有同一块资源,这样就会调用两次析构函数,有可能导致程序崩溃。

例子:

#include 
#include 

using namespace std;

typedef struct student_t
{
    int value;
    string name;
}stu;

/* c++ 版本: c++98 c++03 c++11 c++14 c++17 c++20
 * c   版本: c89 c90 c95 c99 c11 c17
 *
 * 
 */


/*
 * std::auto_ptr 在c++11 以及以上版本被弃用,只能在c++11以下,包括c++03 c++98 等可以使用
 *
 * 
*/
int main(int argc,char * argv[])
{

#if 1
    // smart_ptr pobj(new type(value)); 只能去这样定义,因为其构造函数阻止了其隐式转换
    // 智能指针本质上是一个对象,用来托管对象的地址,可以通过pobj.get()来获取其托管对象的地址
    // ptr.get() 与*ptr 都是取其对象的值
    // auto_ptr 在所有权被转移后,如果访问原来的智能指针,造成程序的错误,因此逐渐被弃用
    // 此外auto_ptr对象并不能作为STL容器的元素,因为C++的STL容器对于容器元素类型的要求是有值语义,即可以赋值和复制,
    // auto_ptr在赋值和复制时都进行了特殊操作;也不能声明为数组,因为auto_ptr无法识别数组,其内部析构时是用的delete而非delete [],它只会析构一次,导致内存泄漏:
    // auto_ptr意义不明确,使用浅拷贝方式时,两个对象拥有同一块资源,这样就会调用两次析构函数,有可能导致程序崩溃


    // 不适应auto_ptr的指针
    /*
     *  ①auto_ptr之间不能共享拥有权

            一个auto_ptr不能指向另一个auto_ptr所拥有的对象,否则,当第一个指针删除该对象后,另一个指针就会指向一个已经被销毁的对象,此时如果再使用那个指针进行读写操作,就会引发错误。

        ②不存在针对array而设计的auto_ptr

            auto_ptr不可以指向array,因为auto_ptr是通过delete而不是delete[ ]来释放其所拥有的对象。

        ③auto_ptr并不是一个全能的、“四海通用”型的指针

            并非任何适用智能型指针的地方,都适用auto_ptr。

        ④auto_ptr不满足STL容器对其元素的要求

            由于在拷贝和赋值动作之后,原本的auto_ptr和新产生的auto_ptr并不相等。原本的auto_ptr会交出拥有权,而不是拷贝给新的auto_ptr。
     *
     * 
    */
    auto_ptr ptr(new int(100));
    
    // ptr.release(); // 取消对对象的托管
    
    int *ptr1 = ptr.get();
    
    cout << "ptr1: " << ptr1 << " " << *ptr1 << endl;

    cout << "ptr: " << ptr.get() << " " << *ptr << endl;
    *ptr = 20;
    cout << *ptr << endl;
    
    cout << "**********" << endl;
    auto_ptr ptr3(ptr); //转移对象的所有权

    cout << "ptr3: " << ptr3.get() << " " << *ptr3 << endl;   
    // cout << *ptr << endl;  // 此时ptr已经被清空
    auto_ptr ptr4 = ptr3; //转移对象的所有权 ,这里是重载赋值
    cout << "ptr4: " << ptr4.get() << " " << *ptr4 << endl;   
    // cout << *ptr3 << endl;   // 此时ptr3已经被清空

    // ptr4.reset(obj) 用来替换智能指针管理的对象,如果传入的为空,则释放托管对象资源并置空,如果传入另一指针,先释放前一指针,再托管后面的。
    ptr4.reset(new int(67));
    cout << "After ptr4: " << ptr4.get() << " " << *ptr4 << endl; 
    
    stu *stu1 = new stu();

    stu1->name = "Tom";
    stu1->value = 19;

    auto_ptr stu_ptr1(stu1); //用stu_ptr1来管理所托管的对象
    // auto_ptr stu_ptr2(stu1);
    
    cout << "name: " << stu1->name << " years_old: " << stu1->value << endl;
    cout << "name: " << stu_ptr1->name << " years_old: " << stu_ptr1->value << endl;

      
#endif

    return 0;
}

        运行结果:

C++面向对象(OOP)编程-智能指针_第3张图片

4.2 unique_ptr

        unique_ptr采用独享语义,在任何给定时刻,只能有一个指针管理内存。当指针超出作用域时,内存将自动释放,而且该类型的指针不可copy,只可以move。

        (1)unique_ptr 不支持拷贝和赋值,只支持转移

        (2)unique_ptr 不支持拷贝构造函数和赋值重载函数,禁用掉,只能转移使用

        (3)使用原始指针申请的动态内存不会在程序结束时,自动释放会造成内存泄露,unique_ptr会在程序运行周期结束时自动调用析构函数来释放掉其申请的内存,不易造成内存的泄漏

一个例子:

#include 
#include 


using namespace std;

typedef struct ARRAY_t
{
    int data[10];
}ARRAY;


int main(int argc, char *argv[])
{
    int *pp = new int(19);
    unique_ptr p1(new int(19));
    // unique_ptr p1 = pp; // 不能这样定义,因为其析构阻止了隐式定义

    // unique_ptr p3 = p1; // 不能这样定义,因为unique_ptr不支持拷贝赋值,只支持转移
    // unique_ptr p3(p1); // 不能这样定义,因为unique_ptr不支持拷贝赋值,只支持转移


    int *p2 = p1.get();// 普通指针

    printf("value: %d  %d \n",*p1.get(),*p2);
    printf("addr: %p %p \n",p1.get(),p2);

    unique_ptr p3;
    // p1.release();//释放 p1 的所有权
    p1.reset(new int(90));  // p1.reset(obj) 用来替换智能指针管理的对象,如果传入的为空,则释放托管对象资源并置空,如果传入另一指针,先释放前一指针,再托管后面的。
    p3.swap(p1);// 将p1的所有权转移给p3,p1设置为nullptr
    
    if (p1.get() == nullptr)
    {
        printf("P1 is moved!\n");
    }
    int *p4 = p3.get();// 普通指针
    
    // if (p1.)
    printf("value: %d %d \n",*p3.get(),*p4);
    printf("addr: %p %p \n",p3.get(),p4);


    // 对数组的操作,auto_ptr不能对数组操作,因为其定义为delete obj 没有 delete[] obj
    ARRAY data1;

    for (int i = 0;i < 10;i++)
    {
        data1.data[i] = i + 2;
    }
    unique_ptr p6(new ARRAY(data1));

    printf("value: %d \n",p6.get()->data[8]);

    

    return 0;

}

        运行结果:

        分析上述结果:

        unique_ptr是独享所有权,不支持copy包括构造函数的拷贝,支持move,相对于auto_ptr,它支持数组。

4.3 shared_ptr

        shared_ptr通过引用计数,析构释放内存空间,实现对内存的动态管理

        (1)一块空间可以由多个shared_ptr管理,通过引用计数的方式进行管理,直到引用计数为0才会删除掉该对象

        (2)一个shared_ptr可以管理一块空间,当一个shared_ptr指向新的对象时,原来的对象的所有的shared_ptr 的引用数减1

        (3)shared_ptr可能会有循环引用问题,循环引用问题可以通过weak_ptr解决

        (4)普通指针不能直接赋值给shared_ptr

        (5)shared_ptr支持copy转移等

一个例子:

#include 
#include 

using namespace std;


int main(int argc, char *argv[])
{

    shared_ptr p1(new int(10));
    int *p2 = p1.get();

    shared_ptr p3 = p1;
    shared_ptr p4(p1);

    printf("Value: %d %d \n",*p1.get(),*p2);
    printf("Addr: %p %p \n",p1.get(),p2);
    printf("p1.count: %ld \n",p1.use_count());

    // p3.reset();
    printf("Value: %d %d \n",*p4.get(),*p2);
    printf("Addr: %p %p \n",p4.get(),p2);
    printf("p1.count: %ld \n",p4.use_count());

    shared_ptr p5(new int(88));

    shared_ptr p6 = p5;
    // p4.swap(p5); // 支持所有权的转移
    p4 = p5;
    
    printf("p1.count: %ld p3.count: %ld \n",p4.use_count(),p3.use_count());
    printf("value p4: %d \n",*p4.get());
    return 0;

}

        运行结果:

C++面向对象(OOP)编程-智能指针_第4张图片

分析代码:

        很明显可以看出,shared_ptr支持转移和copy等。通过引用计数来实现对一个对象的管理。

4.4 weak_ptr

        弱引用的智能指针

        share_ptr虽然已经很好用了,但是有一点share_ptr智能指针还是有内存泄露的情况,当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。

        weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的shared_ptr, weak_ptr只是提供了对管理对象的一个访问手段。

        weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。

一个例子;

#include 
#include 


#define CIRCULAR_REFER

using namespace std;

#ifdef CIRCULAR_REFER
struct Node
{
    shared_ptr prev;
    shared_ptr next;
};

#else
struct Node
{
    weak_ptr prev;
    weak_ptr next;
};
#endif


int main(int argc, char *argv[])
{

    shared_ptr node1(new Node);
    shared_ptr node2(new Node);

    std::cout << "node1.use_count: " << node1.use_count() << std::endl;
    std::cout << "node2.use_count: " << node2.use_count() << std::endl;
    node1->next = node2;
    node2->prev = node1;

    std::cout << "node1.use_count: " << node1.use_count() << std::endl;
    std::cout << "node2.use_count: " << node2.use_count() << std::endl;

    std::cout << "WEAK node2.use_count: " << node2->prev.use_count() << std::endl;

    std::cout << "******************" << std::endl;
    
    
    return 0;

}

        运行结果:

CIRCULAR_REFER 被定义时,shared_ptr进入循环引用        

CIRCULAR_REFER没有被定义时,使用weak_ptr解决循环引用

5 简单对比4种智能指针

        std::unique_ptr是一种独占式智能指针,它拥有对资源的唯一所有权。这意味着一份资源只能由一个std::unique_ptr对象管理,不能被复制,只能通过移动语义来转移所有权。通常用于管理动态分配的内存资源,确保资源在超出作用域时能被正确释放。

        std::shared_ptr允许多个指针共享同一资源,这是通过使用引用计数实现的。只有指向动态分配的对象的指针才能交给shared_ptr对象托管。将指向普通局部变量、全局变量的指针交给shared_ptr托管,编译器会报错。当没有任何shared_ptr对象再指向某一对象时,该对象会被自动删除以避免内存泄漏。

        std::weak_ptr是一种不控制资源的弱引用智能指针,它主要是为了解决shared_ptr可能会出现的循环引用问题而设计的。weak_ptr主要用于解决shared_ptr的循环引用问题,它不会增加对象的引用计数。

        已被摒弃的auto_ptr是C++98提出的,但由于存在一些问题(如无法处理环形数据结构等),在C++11中已被unique_ptr替代。

6 常见面试题

6.1 智能指针的分类?以及各自的特点

        最早在c++98引入auto_ptr智能指针,但不太完善,在c++11弃用,c++17彻底删除。所以现在智能指针一般指的是C++11后引入的智能指针。

        几种智能指针:

  • auto_ptr:已经不用了
  • unique_ptr:独占式指针,同一时刻只能有一个指针指向同一个对象
  • shared_ptr:共享式指针,同一时刻可以有多个指针指向同一个对象
  • weak_ptr:用来解决shared_ptr相互引用导致的死锁问题

6.2 unique_ptr 能否被另一个 unique_ptr 拷贝呢?


        不能,因为它把它的拷贝构造函数private了。但是它提供了一个移动构造函数,所以可以通过std::move将指针指向的对象交给另一个unique_ptr,转交之后自己就失去了这个指针对象的所有权,除非被显示交回。

6.3 unique_ptr 和 shared_ptr 的区别


(1)所有权

        unique_ptr代表的是专属所有权,不支持复制和赋值。但是可以移动shared_ptr 代表的是共享所有权,shared_ptr 是支持复制的和赋值以及移动的。
(2)资源消耗上

        unique_ptr 在默认情况下和裸指针的大小是一样的。所以 内存上没有任何的额外消耗,性能是最优的,我们大多数场景下用到的应该都是 unique_ptr。
        shared_ptr 的内存占用是裸指针的两倍。因为除了要管理一个裸指针外,还要维护一个引用计数。因此相比于 unique_ptr, shared_ptr 的内存占用更高。在使用 shared_ptr 之前应该考虑,是否真的需要使用 shared_ptr, 而非 unique_ptr。

6.4 unique_ptr = unique_ptr和shared_ptr=shared_ptr这两个操作有什么后果呢?

        unique_ptr 

        这个操作是不允许的,因为unique_ptr它的原理是将拷贝构造和拷贝赋值私有化,但是它提供了移动构造和移动赋值。所以如果你想要使用=赋值,必须先把右边的用std::move包裹一下,这样右边的unique_ptr就会失去所有权,左边的unique_ptr就会得到对应对象的所有权。
        shared_ptr

        对于左边的指针,它会将自己的引用计数减一,然后检测一下是不是减到了0,如果是,那么delete所管理的对象,然后将右边的引用计数和管理对象赋值给左边,此时两边指向同一个对象,共享同一个引用计数,然后引用计数++。

6.5 shared_ptr 的移动赋值时发生了什么事情

  • 首先它会检查本指针和参数指针是不是同一个对象,如果是,直接返回
  • 然后,先把本指针的引用变量–,如果发现减到了0,就把参数指针和参数引用变量析构掉并置NULL
  • 最后,本指针和参数指针指向同一个对象以及引用计数,然后引用计数自增1

6.6 shared_ptr 什么时候会被释放掉,就是什么时候引用次数为0?

  • 在一个shared_ptr析构时,或者被赋值时,强引用计数会减1。减到0就delete资源

6.7 shared_ptr 你是怎么实现的


        shared_ptr 内部是利用引用计数来实现内存的自动管理,每当复制一个 shared_ptr,引用计数会 + 1。当一个 shared_ptr 离开作用域时,引用计数会 - 1。当引用计数为 0 的时候,则 delete 内存。

        关键在于多个对象持有同一个引用对象,第一次创建指针的时候可以new一个int,然后大家都有一个int指针就可以共用了。

        shared_ptr应该要有三个成员:

        裸指针:指向所要管理的对象
        强引用计数:就是一个int指针,记录了有多少个shared_ptr指向裸指针
        弱引用计数:也是一个int指针,记录了有多少个weak_ptr指向裸指针

6.8 shared_ptr是不是线程安全

  • 不是
  • 引用计数的增减是原子操作没问题,但是shared_ptr的读写本身不只包括引用计数操作,还包括资源所有权的操作,这两个操作合起来不是原子的
  • 如果要求线程安全必须加锁

一个shared_ptr错误使用引起的线程不安全:

#include 
#include 
#include 
#include 

struct Foo
{
    Foo(int i):i_(i){}
    void print() {std::cout << i_ << std::endl;}
    int i_;
};

int main(int argc, char const *argv[])
{
    {
        auto shptr = std::make_shared(42);
        std::thread([&shptr](){
            std::this_thread::sleep_for(std::chrono::seconds(1));
            shptr->print();
        }).detach();
    }
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return 0;
}

        编译报错

        将传入线程的智能指针的引用变成对象就不会报错,因此,对于智能指针,应该尽量避免传递引用。

6.9 sharedPtr在64位操作系统下大小有多大

        在64位系统中,一个指针变量的大小是8个字节。因此,一个shared_ptr变量的大小为16个字节。这是因为一个shared_ptr变量包含了两个指针,一个用于指向对象,另一个用于管理引用计数。

6.10 shared_ptr和weak_ptr之间是什么关系?

       weak_ptr是用来辅助shared_ptr的,每一个weak_ptr它指向weak_ptr而不是实际的操作函数


6.11 为什么推荐用make_shared创建指针


        优点是:

        它分配的时候,只分配一次,而shared_ptr的构造函数需要分配两次。效率更高更安全。
缺点是:
        如果有weak_ptr不推荐使用,否则会出现知道最后一个weak_ptr被释放了才真正去释放管理的。

6.12 为什么要用 shared_from_this?

        我们往往会需要在类内部使用自身的 shared_ptr,例如:

class Widget
{
public:
    void do_something(A& a)
    {
        a.widget = 该对象的 shared_ptr;
    }
}

                我们需要把当前 shared_ptr 对象同时交由对象 a 进行管理。意味着,当前对象的生命周期的结束不能早于对象 a。因为对象 a 在析构之前还是有可能会使用到 a.widget。

如果我们直接 a.widget = this;, 那肯定不行, 因为这样并没有增加当前 shared_ptr 的引用计数。shared_ptr 还是有可能早于对象 a 释放。

        如果我们使用 a.widget = std::make_shared(this);,肯定也不行,因为这个新创建的 shared_ptr,跟当前对象的 shared_ptr 毫无关系。当前对象的 shared_ptr 生命周期结束后,依然会释放掉当前内存,那么之后 a.widget 依然是不合法的。

        对于这种,需要在对象内部获取该对象自身的 shared_ptr, 那么该类必须继承 std::enable_shared_from_this。代码如下:

class Widget : public std::enable_shared_from_this
{
public:
    void do_something(A& a)
    {
        a.widget = shared_from_this();
    }
}

这样才是合法的做法。

6.13 weak_ptr的原理?weak_ptr的使用场景


        shared_ptr中有一个成员时,弱引用计数。往weak_ptr被赋值时,弱引用计数自增1

        (1)用来解决悬空指针问题。通过std::shared_ptr管理数据并将std::weak_ptr提供给数据用户,用户可以通过expired()或者lock()来检测数据的有效性
        (2)打破shared_ptr相互引用导致死锁的问题。方法:将任意一个改为weak_ptr
        (3)有时候我们需要“如果对象还活着,就调用它的成员函数,否则忽略之”的语意
        (4)缓存对象

6.14 weak_ptr如何检测指针是否被销毁


        expired():

        判断强引用计数是否为0
如果返回true,那么被观测的对象(也就是shared_ptr管理的资源)已经不存在了


6.15 如何将weak_ptr转换为shared_ptr


        用lock():

        如果expired()为true,返回一个空shared_ptr,否则返回非空shared_ptr。

你可能感兴趣的:(C/C++精进之路,c++,开发语言,智能指针)