• C++智能指针


    智能指针模板类

    智能指针是行为类似于指针的类对象,但这种对象还有其他功能。本文章介绍三个可帮助管理动态内存分配的只能指针模板。先来看需要哪些功能以及这些功能是如何实现的。请看下面的函数

    void remodel(std::string & str)

    {

             std::string *ps = new std::string(str);

             ...

             str = ps;

             return ;

    }

    你可能发现了其中的缺陷。每当调用时,该函数都分配堆中的内存,,但从不回收,从而导致内存泄露。你可能也知道解决之道------只要别忘了在return语句前添加下面的语句,以释放分配的内存即可:

    delete ps;

    然而,但凡涉及“别忘了”的解决方法,很少是最佳的。因为你有时可能忘了,有时可能记住了,但可能在不经意间删除或注释了这些代码。即使确实没有忘记,也可能有问题。请看下面的变体:

    void remodel(std::string & str)

    {

             std::string *ps = new std::string(str);

             ...

             if (weird_thing())

             {

                       throw exception();

             }

             str = *ps;

             delete ps;

             return;

    }

     当出现异常时,delete将不被执行,因此也将导致内存泄露。

    当remodel()这样的函数终止(不管是正常终止,还是由于出现了异常而终止),本地变量都将从栈内存中删除------因此指针ps占据的内存将被释放。如果ps指向的内存也被释放,那该有多好啊。如果ps有一个析构函数,该析构函数在ps过期时释放他指向的内存。因此,ps的问题在于,它只是一个常规指针,不是有析构函数的类对象。如果它是对象,则可以在对象过期时,让它的析构函数删除指向的内存。这正是auto_ptr、unique_ptr、shared_ptr背后的思想。模板auto_ptr是C++98提供的解决方案,C++已将其摒弃,并提供了另外两种解决方案。然而,虽然auto_ptr被摒弃,但它已使用了多年同时,如果您的编译器不支持其他两种解决方案,auto_ptr将是唯一的选择。

    使用智能指针

    这三个智能指针模板(auto_ptr,unique_ptr,shared_ptr)都定义了类似指针的对象,可以将new获得(直接或间接)的地址赋给这个对象。当智能指针过期时,其析构函数将使用delete来释放内存。因此,如果将new返回的地址赋给这些对象,将无需记住稍后释放这些内存;在智能指针过期时,这些内存将自动被释放。下图说明了auto_ptr和常规指针在行为方面的差别;shared_ptr和unique_ptr的行为与auto_ptr相同。

    要创建智能指针对象,必须包含头文件memory,该文件模板定义。然后使用通常的模板语法来实例化所需类型的指针。例如,模板auto_ptr包含如下构造函数:

    template<class X>

    class auto_ptr

    {

             explicit auto_ptr(X *p = 0)throw();

             ...

            

    };

    throw()意味这构造函数不会引发异常:与auto_ptr一样,throw()也被摒弃。因此,请求X类型的auto_ptr将获得一个指向X类型的auto_ptr:

    auto_ptr<double>pd(new double);//pd an auto_ptr to double

                                                                        //(use in place of double *pd)

    auto_ptr<string>ps(new string);//ps an auto_ptr to string

                                                                        //(use in place of string *ps)

    new double是new返回的指针,指向新分配的内存块。它是构造函数auto_ptr<double>的参数,即对应于原型中形参p的实参。同样,new string也是构造函数的实参。其他两种智能指针使用同样的语法:

    unique_ptr<double> pdu(new double);//pdu an unique_ptr to double

    shared_ptr<string> pss(new string);//pss a shared_ptr to string

    因此。要转换remodel()函数,应按下面3个步骤进行:

    1. 包含头文件memory;
    2. 将指向string的指针替换为指向string的智能指针对象;
    3. 删除delete语句。

    下面是使用auto_ptr修改该函数的结果:

    #include <memory>

    void remodel(std::string & str)

    {

             std::auto_ptr<std::string>ps (new std::string(str));

             …

             if (weird_thing())

    {

             throw exception();

    }

    str = *ps;

    //delete ps; NO LONGER NEEDED

    return ;

    }

    注意到智能指针模板位于命名空间std中。下面是一个简单的程序,演示了如何使用全部三种智能指针。每个智能指针都放在一个代码块内,这样离开代码块时,指针将过期。Report类使用方法报告对象的创建和销毁。

    //smrtptrs.cpp -- using three kinds of smart pointers

    //requires support of C++11 shared_ptr and unique_ptr

    #include <iostream>

    #include <string>

    #include <memory>

    using namespace std;

    class Report

    {

    public:

             Report(const string s) : str(s)

             {

                       cout << "Object created!" << endl;

             }

             ~Report()

             {

                       cout << "Object deleted!" << endl;

             }

             void comment() const

             {

                       cout << str << endl;

             }

    private:

             string str;

    };

    int main()

    {

             {

                       auto_ptr<Report> ps(new Report("using auto_ptr"));

                       ps->comment();

             }

             {

                       shared_ptr<Report> ps(new Report("using shared_ptr"));

                       ps->comment();

             }

             {

                       unique_ptr<Report> ps(new Report("using unique_ptr"));

                       ps->comment();

             }

             system("pause");

             return 0;

    }

    程序输出结果:

     

    所有智能指针类都一个explicit构造函数,该构造函数将指针作为参数。因此不需要自动将指针转换为智能指针对象:

    shared_ptr<double> pd;

    double *p_reg = new double;

    pd = p_reg; //not allowed (implicit conversion)

    pd = shared_ptr<double>(p_reg);//allowed(explicit conversion)

    shared_ptr<double> pshared = p_reg;//not allowed(implicit conversion)

    shared_ptr<double> pshared(p_reg);//allowed(explicit conversion)

    由于智能指针模板类的定义方式,智能指针对象的很多方面都类似于常规指针。例如,如果ps是一个智能指针对象,则可以对它执行解除引用操作(*ps)、用它来访问结构成员(ps->puffIndex)、将他赋给指向相同类型的常规指针,还可以将智能指针对象赋给另一个同类型智能指针对象,但将引起一个问题,这将在下面进行讨论。

    但在此之前,先说说对全部三种智能指针都应避免的一点:

    string vacation(“I wandered lonely as a cloud”);

    shared_ptr<string> pvac(&vacation);//NO;

     

    pvac过期时,程序将把delete运算符用于非堆内存,这是错误的。

    有关智能指针的注意事项

    先来看下面的赋值语句:

    auto_ptr<string> ps(new string(“I reigned lonely as a cloud”));

    auto_ptr<string> vocation;

    vocation = ps;

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

    定义赋值运算符,使之执行深复制。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的副本。

    建立所有权(ownership)概念,对于特定的对象,只能有一个智能指针可拥有它,这样只有拥有和unique_ptr的策略,但unique_ptr的策略更严格。

    创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数(reference counting)。这是shared_ptr采用的策略

    当然,同样的策略也适用于复制构造函数。

    每种方法都有其用途。如下程序不适合使用auto_ptr的实例

    #include <iostream>

    #include <string>

    #include <memory>

    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 Run")),

                       auto_ptr<string> (new string("Turkey Errors")),

                       auto_ptr<string> (new string("Goose Eggs"))

             };

             auto_ptr<string> pwin;

             pwin = films[2];

             cout << "The nominees for best avian baseball film are ";

             for (int i = 0; i < 5; i++)

             {

                       cout << *films[i] << endl;

             }

             cout << "The winner is " << *pwin << endl;

             cin.get();

             return 0;

    }

    该程序的输出:

    这里的问题在于,下面的语句所有权从film[2]转让给pwin:

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

    这导致films[2]不再引用该字符串。在auto_ptr放弃对象的所有权后,便可能使用它来访问该对象。当程序打印films[2]指向的字符串时,却发现这是一个空指针,这显然讨厌的意外。

    如果在上面程序中使用shared_ptr代替auto_ptr,则程序将正常运行,其输出如下:

     

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

    如果使用unique_ptr结果会怎么样呢?unique_ptr也采用所有权模型,但使用unique_ptr时。程序不会等到运行阶段崩溃,而是出现语法错误,如下

    pwin = films[2];

     

    unique_ptr为何优于auto_ptr

    请看下面的语句:

    auto_ptr<string> p1(new string("auto"));//#1

    auto_ptr<string> p2;//#2

    p2 = p1;//#3

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

    下面来看使用unique_ptr的情况:

    unique_ptr<string> p3(new string("auto"));//#4

    unique_ptr<string> p4;//#5

    p3 = p4;//#6

    编译器认为语句#6非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全(编译阶段错误比潜在的程序崩溃更安全)

    但有时候,有一个智能指针赋给另一个并不会留下危险的悬挂指针。假设有如下函数定义:

    unique_ptr<string> demo(const char *s)

    {

             unique_ptr<string> temp(new string(s));

             return temp;

    }

    并假设编写了如下代码:

    unique_ptr<string> ps;

    ps = demo("Uniquely special");

    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将留下悬挂的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<string>对象:

             unique_ptr<string> ps1, ps2;

             ps1 = demo("Uniquely special");

             ps2 = move(ps1);

             ps1 = demo(" and more");

             cout << *ps2 << *ps1 << endl;

    相比于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 []

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

  • 相关阅读:
    @Controller @RestController
    HOMEWORD2
    cudnn、cuda
    jupyter使用
    python学习——os模块
    python学习——导入模块、__name__
    python学习——文件
    python学习——函数、内置函数
    python学习——for循环的应用
    python学习——字典、集合、数据类型总结
  • 原文地址:https://www.cnblogs.com/SimonKly/p/7569828.html
Copyright © 2020-2023  润新知