• 第16章、 string类和标准模板库


    16.2 智能指针模板类

    本节介绍三个可帮助动态内存分配的智能指针模板。先来看需要哪些功能以及这些功能是如何实现的。请看下面的函数:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    不是有析构函数的对象。如果它是对象,则可以在对象过期时,让它的析构函数删除指向的内存。这正是auto_ptr、unique_ptr和shared_prt背后的思想。模板auto_ptr是C++98提供的解决方案,C++11已将其摈弃,并提供了另外两种解决方案。然而,虽然auto_ptr被摒弃,但它多年使用;同时,如果你的编译器不支持其他两种解决方案,auto_ptr将是唯一的选择。

    16.2.1 使用智能指针

    这三个智能指针模板(auto_ptr、unique_ptr和shared_prt)都定义了类似指针的对象,可以将new获得(直接或间接)的地址赋给这种对象。当智能指针过期时,其析构函数将在使用delete来释放内存。因此,如果将new返回的地址赋给这些对象,将无需记住稍后释放这些内存:在智能指针过期时,这些内存将自动被释放。下图说明了auto_ptr和常规指针在行为方面的差别;unique_ptr和shared_prt的行为和auto_ptr相同。
    在这里插入图片描述
    在这里插入图片描述
    要创建智能指针对象,必须要包含头文件memory,该文件模板定义。然后使用通常的模板语法来实例化所需类型的指针。例如,模板auto_ptr包含如下构造函数:

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    其他两种智能指针使用同样的语法:

    unique_ptr<double> pdu(new double);
    shared_ptr<double> pss(new string);
    
    • 1
    • 2

    在这里插入图片描述
    在这里插入图片描述
    下面的程序简单演示了如何使用全部三种智能指针。要编译该程序,编译器必须要支持C++11新增的类shared_ptr和unique_ptr。每个智能指针都放在一个代码块内,这样离开代码块时,指针将过期,Report类使用方法报告对象的创建和销毁。
    程序 16.5

    #include 
    #include
    #include
    using namespace std;
    class Report{
    private:
        string str;
    public:
        Report(const string s):str(s)
        {cout<<"Object created!\n";}
        ~Report(){cout<<"Object deleted!\n";}
        void comment() const {cout<<str<<"\n";}
    
    };
    
    
    int main()
    {
    //    cout << "Hello World!" << endl;
        {
            auto_ptr<Report> ps(new Report ("using auto_ptr"));
            ps->comment();// use -> to invoke a member function
        }
        {
            shared_ptr<Report> ps(new Report ("using shared_ptr"));
            ps->comment();// use -> to invoke a member function
        }
        {
            unique_ptr<Report> ps(new Report ("using unique_ptr"));
            ps->comment();// use -> to invoke a member function
        }
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    程序输出如下:
    在这里插入图片描述
    所有智能指针类都一个explicit构造函数,该构造函数将指针作为参数。因此不需要自动将指针转换为智能指针对象:

        shared_ptr<double> pd;
        double *p_reg=new  double;
        pd=p_reg;// not allowed(implicit conversion)
        pd=shared_ptr<double> (p_reg);//  allowed(implicit conversion)
        shared_ptr<double> pshared=p_reg;// not allowed(implicit conversion)
        shared_ptr<double> pshared(p_reg);//  allowed(implicit conversion)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    由于智能指针模板类的定义方式,智能指针对象的很多方面都类似于常规指针。例如,如果ps是一个智能指针对象,则可以对它执行解除引用操作(*ps)、用它来访问成员(ps->puffIndex)、将它赋给指向相同类型的常规指针。还可以将智能指针对象赋给另一个同类型的智能指针对象,但将引起一个问题,这将在下一节进行讨论。
    但在此之前,先说说对全部三种智能指针都应该避免的一点:

        string vacation("I wantered lonely as a cloud");
        shared_ptr<string> pvac(&vacation);//NO ! 
    
    • 1
    • 2

    pvac过期时,程序将把delete运算符用于非堆内存,这是错误的。
    就上面的三种使用智能指针的程序16.5的情况而言,三种智能指针都能满足要求,但情况并非总是这样简单。

    16.2.2 有关智能指针的注意事项

    为何有三种智能指针呢?实际上有4种,但本书不讨论weak_ptr。为何摒弃auto_ptr呢?
    先来看下面的赋值语句:

        auto_ptr<string> ps(new  string("I reigned lonely as a cloud.") );
        auto_ptr<string> vocation;
        vocation=ps;
    
    • 1
    • 2
    • 3

    上述赋值语句将完成什么工作呢?如果ps和vocation是常规指针,则两个指针将指向同一个string对象,这是不能接受的,因为程序将试图删除同一个对象两次—— 一次是ps过期,另一次是vocation过期时,要避免这种问题,方法有多种。

    1、定义赋值运算符,使之执行深复制。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的副本。
    2、建立所有权概念,对于特定的对象,只能有一个智能指针可以拥有它,这样只有拥有对象的智能指针的构造函数会删除该对象。然后,让赋值操作转让所有权。这就是用于auto_ptr和unique_ptr的策略,但unique_ptr的策略更加严格。
    3、创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。例如,赋值时,计数将加1,而指针过期时,计数将减1.仅当最后一个指针过期时,才调用delete。这是shared_ptr采用的策略。

    当然,同样的策略也适用于复制构造函数。
    每种方法都有其用途,程序清单16.6是一个不适合使用auto_ptr的示例。

    程序清单16.6 fowl.cpp

    #include 
    #include
    #include
    using namespace std;
    
    int main()
    {
    
        auto_ptr<string> films[5]=
        {
            auto_ptr<string> (new string ("Fowl Balls") ),
            auto_ptr<string> (new string ("Duck Walks") ),
            auto_ptr<string> (new string ("Chicken Runs") ),
            auto_ptr<string> (new string ("Turkey Errors") ),
            auto_ptr<string> (new string ("Goose Eggs") )
        };
    
        auto_ptr<string> pwin;
        pwin =films[2];  //films[2] loses ownership
    
        cout<<"The nominees for best avian baseball film are\n";
    
        for (int i=0;i<5;i++) {
            cout<<*films[i]<<endl;
        }
    
        cout<<"The winner is"<< *pwin<<"!\n";
        cin.get();
    
        return 0;
    
    //    cout << "Hello World!" << endl;
    //    return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    下面是该程序的输出:

    The nominees for best avian baseball film are
    Fowl Balls
    Duck Walks
    Segmentation fault (core dumped)
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    消息core dumped表明,错误地使用auto_ptr可能导致问题(这种代码的行为是不确定的,其行为可能随系统而异)。这里的问题在于,下面的语句将所有权films[2]转让给 pwin :

     pwin =films[2];  //films[2] loses ownership
    
    • 1

    这导致films[2]不再引用该字符串。在auto_ptr放弃对象的所有权后,便可能使用它来访问该对象。当程序打印films[2]指向的字符串时,却发现这是一个空指针,这显然是个意外。
    如果在程序清单16.6使用shared_ptr代替auto_ptr,则程序正常运行。其输出如下

    在这里插入图片描述

    使用shared_ptr的程序

    #include 
    #include
    #include
    using namespace std;
    
    int main()
    {
    
        //auto_ptr films[5]=
        shared_ptr<string> films[5]=
        {
            auto_ptr<string> (new string ("Fowl Balls") ),
            auto_ptr<string> (new string ("Duck Walks") ),
            auto_ptr<string> (new string ("Chicken Runs") ),
            auto_ptr<string> (new string ("Turkey Errors") ),
            auto_ptr<string> (new string ("Goose Eggs") )
        };
    
        //auto_ptr pwin;
        shared_ptr<string> pwin;
        pwin =films[2];  //films[2] loses ownership
    
        cout<<"The nominees for best avian baseball film are\n";
    
        for (int i=0;i<5;i++) {
            cout<<*films[i]<<endl;
        }
    
        cout<<"The winner is"<< *pwin<<"!\n";
        cin.get();
    
        return 0;
    
    //    cout << "Hello World!" << endl;
    //    return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    这次pwin和films[2]指向同一个对象,而引用计数从1增加到2。在程序末尾,后声明的pwin首先调用其析构函数,该析构函数将引用计数降低到1. 然后,shared_ptr数组的成员被释放,对films[2]调用析构函数时,将引用计数降低到0 ,并释放以前分配的空间。

    因此使用shared_ptr时,程序运行正常;而使用auto_ptr时,该程序在运行阶段崩溃。如果使用unique_ptr,结果将如何呢?与auto_ptr一样,unique_ptr也将采用所有权模型。但使用unique_ptr时,程序不会等到运行阶段崩溃,而在编译器因下述代码行出现错误:

    pwin = films[2];
    
    • 1

    16.2.3 unique_ptr为何优于auto_ptr

    请看下面的语句:

        auto_ptr<string> p1(new string("auto"));  //#1
        auto_ptr<string> p2;                      //#2
        p2=p1;                                    //#3 
    
    • 1
    • 2
    • 3

    在语句#3中,p2接管string对象的所有权后,p1的所有权将被剥夺。前面说过,这是一件好事,可以防止p1和p2的析构函数试图删除同一个对象;但如果程序随后试图使用p1,这将是件坏事,因为p1不再指向有效的数据。
    下面来看使用unique_ptr的情况:

            unique_ptr<string> p3(new string("auto"));  //#4
            unique_ptr<string> p4;                      //#5
            p4=p3;                                      //#6  
    
    • 1
    • 2
    • 3

    编译器认为语句#6非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全(编译阶段错误比潜在的程序崩溃更加安全)。
    但有时候,将一个智能指针赋给另一个并不会留下危险的悬挂指针。假设有如下函数定义:

    unique_ptr<string> demo(const char *s){
        unique_ptr<string> temp(new string(s));
        return temp;
    }
    
    • 1
    • 2
    • 3
    • 4

    并假设编写了如下代码

        unique_ptr<string> ps;
        ps=demo("Uniquely special");
    
    • 1
    • 2

    demo()返回一个临时unique_ptr,然后ps接管了原本归返回的unique_ptr所有的对象,而返回unique_ptr被销毁。这没有问题,因为ps拥有了string对象的所有权。但这里的另一个好处是,demo()返回的临时unique_ptr很快被销毁,没有机会使用它来访问无效的数据。换句话说,没有理由禁止这种赋值。神奇的是,编译器确实允许这种赋值。

    总之,程序试图将一个unique_ptr赋给另一个时,如果源unique_ptr是个临时右值,编译器允许这样做,如果源unique_ptr将存在一段时间,编译器将禁止这样做:

        unique_ptr<string> pu1(new string "Hi ho!" );
        unique_ptr<string> pu2;
        pu2=pu1;             //#1 not allowed
        
        unique_ptr<string> pu3;
        pu3=unique_ptr<string> (new string "Yo!" )//#2 allowed
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    语句#1将留下悬挂的unique_ptr(pu1),这可能导致危害。语句#2不会留下悬挂的unique_ptr ,因为它调用unique_ptr的构造函数,该构造函数创建的临时对象在其所有权转让给pu3后就会销毁。这种随情况而异的行为表明,unique_ptr优于允许两种赋值的auto_ptr。这也是禁止(这只是一种建议,编译器并不禁止)在容器对象中使用auto_ptr,但允许使用unique_ptr的原因。如果容器算法试图对包含unique_ptr的容器执行类似于语句#1的操作,将导致编译错误;如果算法试图执行类似于语句#2的操作,则不会有任何问题。而对于auto_ptr,类似于语句#1的操作可能导致不确定的行为和神秘的崩溃。

    当然,你可能确实想执行类似于语句#1的操作。仅当以非智能的方式使用遗弃的智能指针(如解除引用时,),这种赋值才不安全。要安全地重用这种指针,可以给它赋值。C++有一个标准库函数std::move();让你能够将一个unique_ptr赋值给另一个,下面是前述demo()函数的例子,该函数返回一个unique_ptr对象:

        unique_ptr<string> ps1,ps2;
        ps1=demo("Uniquely special");
        ps2=move(ps1);            //enable assigment
        ps1=demo(" and more");
        cout<<*ps2 <<*ps1 <<endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    你可能会问,unique_prt如何能够区分安全和不安全的用法呢?答案是它使用了C++11新增的移动构造函数和右值引用。
    相比于auto_ptr,unique_ptr还有另外一个优点。它有一个可用于数组的变体。别忘了,必须将delete和new配对,将delete[ ]和new [ ]配对。模板auto_ptr使用delete而不是delete[ ],因此只能与new 一起使用,而不能与new [ ] 一起使用。但unique_ptr有使用new [ ]和delete [ ] 的版本。

    unique_ptr<double []>pda(new double (5) );// will use delete []
    
    • 1

    警告:使用new分配内存时,才能使用auto_ptr和shared_ptr,使用new[ ]分配内存时,不能使用它们.。 不使用new分配内存时,不能使用auto_ptr或shared_ptr ;不使用new或new[ ]分配内存时,不能使用unique_ptr。

    16.2.4 选择智能指针

    应该使用哪种智能指针呢?如果程序要使用多个指向同一个对象的指针,选择shared_ptr 。这样的情况包括:有一个指针数组,并使用一些辅助指针来标识特定的元素,如最大的元素和最小的元素;两个对象包含都指向第三个对象的指针;STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出警告)和auto_ptr(行为不确定)。如果你的编译器没有提供shared_ptr,可以使用Boost库提供的shared_ptr.

    如果程序不需要多个指向同一个对象的指针,则可以使用unique_ptr。如果函数使用new分配内存,并返回指向该内存的指针,将其返回类型声明为unique_ptr是一个不错的选择。这样,所有权将转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。可以将unique_ptr存储到STL容器中,只要不调用将一个unique_ptr复制或赋给另一个的方法或算法(如sort())。例如,可以在程序中使用类似于下面的代码段,这里假设程序包含正确的include和using语句:

    #include
    #include 
    #include
    #include 
    using namespace std;
    
    unique_ptr<int> make_int(int n){
        return unique_ptr<int> (new int (n) );
    }
    void show(unique_ptr<int> &pi){//pass by reference
        cout<<*pi<<' ';
    }
    
    
    int main()
    {
        int size=5;
        vector<unique_ptr<int> > vp(size);
        for (int i=0;i<vp.size();++i) {
            vp[i]=make_int(rand()%1000);//copy temporary unique_ptr
    
        }
        vp.push_back(make_int(rand()%1000));//ok because arg is temporary
    //    foreach (var, container) {
    
    //    }
        for_each(vp.begin(),vp.end(),show);
    
        //cout << "Hello World!" << endl;
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    在这里插入图片描述

    其中的push_back()调用没有问题,因为它返回一个临时unique_ptr,该unique_ptr被赋给vp中的一个unique_ptr。另外,如果按值传递而不是按引用给show函数传递对象,for_each语句将非法,因为这将导致使用一个来自vp的非临时unique_ptr初始化pi,而这是不允许的。前面说过,编译器将发现错误使用unique_ptr的企图。

    在unique_ptr为右值时,可以将其赋值给shared_ptr,这与将一个unique_ptr赋给另一个需要满足的条件相同。与前面一样,在下面的代码中,make_int()的返回类型为unique_ptr:

        unique_ptr<int> pup(make_int(rand()%1000));//ok
        shared_ptr<int> spp(pup);// not allowed,pup an lvalue
        shared_ptr<int> spr(make_int(rand()%1000));//ok
    
    • 1
    • 2
    • 3

    模板shared_ptr包含一个显式构造函数,可用于将右值unique_ptr转换为shared_ptr . shared_ptr将接管原来归unique_ptr 所有的对象。

    在满足unique_ptr要求的条件时,也可以使用auto_ptr,但unique_ptr是更好的选择。
    如果编译器没有unique_ptr,可以考虑使用Boost库提供的scoped_ptr ,它与unique_ptr类似。

    来源:C++ primer plus
    仅供学习 侵删

  • 相关阅读:
    JAVA培训之连接查询之子查询
    C#使用OpenCv(OpenCVSharp)图像中值滤波、高斯滤波、均值滤波、高斯双边滤波处理实例
    Spring Security OAuth2.0 实现分布式系统的认证和授权
    oauth2 授权模式 - 第三方登录
    哈希表— —链式实现
    Mybatis总结--传参二
    AI 已经在污染互联网了。。赛博喂屎成为现实
    常用的数字签名,信息加密算法
    js将时间戳处理为年月日时分秒格式
    自动驾驶学习笔记(九)——车辆控制
  • 原文地址:https://blog.csdn.net/qq_30457077/article/details/126568906