• 【校招面试 之 C/C++】第25题 C++ 智能指针(一)之 auto_ptr


    1、智能指针背后的设计思想

    我们先来看一个简单的例子:

    void remodel(std::string & str)
    {
        std::string * ps = new std::string(str);
        ...
        if (weird_thing())
            throw exception();
        str = *ps; 
        delete ps;
        return;
    }
    

    当出现异常时(weird_thing()返回true),delete将不被执行,因此将导致内存泄露。

    如何避免这种问题?有人会说,这还不简单,直接在throw exception();之前加上delete ps;不就行了。是的,你本应如此,问题是很多人都会忘记在适当的地方加上delete语句(连上述代码中最后的那句delete语句也会有很多人忘记吧),如果你要对一个庞大的工程进行review,看是否有这种潜在的内存泄露问题,那就是一场灾难!
    这时我们会想:当remodel这样的函数终止(不管是正常终止,还是由于出现了异常而终止),本地变量都将自动从栈内存中删除—因此指针ps占据的内存将被释放,如果ps指向的内存也被自动释放,那该有多好啊。
    我们知道析构函数有这个功能。如果ps有一个析构函数,该析构函数将在ps过期时自动释放它指向的内存。但ps的问题在于,它只是一个常规指针,不是有析构凼数的类对象指针。如果它指向的是对象,则可以在对象过期时,让它的析构函数删除指向的内存。

    这正是 auto_ptr、unique_ptr和shared_ptr这几个智能指针背后的设计思想。我简单的总结下就是:将基本类型指针封装为类对象指针(这个类肯定是个模板,以适应不同基本类型的需求),并在析构函数里编写delete语句删除指针指向的内存空间

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

    • 包含头义件memory(智能指针所在的头文件);
    • 将指向string的指针替换为指向string的智能指针对象;
    • 删除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;
    }

    2、auto_ptr 

    智能指针(auto_ptr) 这个名字听起来很酷是不是?其实auto_ptr 只是C++标准库提供的一个类模板,它与传统的new/delete控制内存相比有一定优势,但也有其局限。本文总结的8个问题足以涵盖auto_ptr的大部分内容。

    1. auto_ptr是什么?

    auto_ptr 是C++标准库提供的类模板,auto_ptr对象通过初始化指向由new创建的动态内存,它是这块内存的拥有者,一块内存不能同时被分给两个拥有者。当auto_ptr对象生命周期结束时,其析构函数会将auto_ptr对象拥有的动态内存自动释放。即使发生异常,通过异常的栈展开过程也能将动态内存释放。auto_ptr不支持new 数组

    2. auto_ptr需要包含的头文件?

    #include <memory>

    3. 初始化auto_ptr对象的方法?

    1) 构造函数

    • 将已存在的指向动态内存的普通指针作为参数来构造

        int* p = new int(33);

        auto_ptr<int> api(p);

    • 直接构造智能指针

        auto_ptr< int > api( new int( 33 ) );

    2) 拷贝构造

      利用已经存在的智能指针来构造新的智能指针

      auto_ptr< string > pstr_auto( new string( "Brontosaurus" ) );

      auto_ptr< string > pstr_auto2( pstr_auto );  //利用pstr_auto来构造pstr_auto2

    因为一块动态内存只能由一个智能指针独享,所以在拷贝构造或赋值时都会发生拥有权转移的过程。在此拷贝构造过程中,pstr_auto将失去对字符串内存的所有权,而pstr_auto2将其获得。对象销毁时,pstr_auto2负责内存的自动销毁

    3) 赋值

      利用已经存在的智能指针来构造新的智能指针

      auto_ptr< int > p1( new int( 1024 ) );

      auto_ptr< int > p2( new int( 2048 ) );

      p1 = p2;

      在赋值之前,由p1 指向的对象被删除。赋值之后,p1 拥有int 型对象的所有权。该对象值为2048。 p2 不再被用来指向该对象。需要注意的是这种情况非常不友好,当我们再试图访问

      p2时(例如cout<<*p2;)这时程序在运行时会报错,原因是p2现在已经不再指向任何有效的内存

    4. 空的auto_ptr 需要初始化吗?

    通常的指针在定义的时候若不指向任何对象,我们用Null给其赋值。对于智能指针,因为构造函数有默认值0,我们可以直接定义空的auto_ptr如下:

    auto_ptr< int > p_auto_int;  //不指向任何对象

    5. 防止两个auto_ptr对象拥有同一个对象(一块内存)

    因为auto_ptr的所有权独有,所以下面的代码会造成混乱。

    int* p = new int(0);
    auto_ptr<int> ap1(p);
    auto_ptr<int> ap2(p);

    因为ap1与ap2都认为指针p是归它管的,在析构时都试图删除p, 两次删除同一个对象的行为在C++标准中是未定义的。所以我们必须防止这样使用auto_ptr。

    6. 警惕智能指针作为参数!

    1) 按值传递时,函数调用过程中在函数的作用域中会产生一个局部对象来接收传入的auto_ptr(拷贝构造),这样,传入的实参auto_ptr就失去了其对原对象的所有权,而该对象会在函数退出时被局部auto_ptr删除。如下例:

    void f(auto_ptr<int> ap)

    {cout<<*ap;}
    auto_ptr<int> ap1(new int(0));
    f(ap1);
    cout<<*ap1; //错误,经过f(ap1)函数调用,ap1已经不再拥有任何对象了。

    2) 引用或指针时,不会存在上面的拷贝过程。但我们并不知道在函数中对传入的auto_ptr做了什么,如果当中某些操作使其失去了对对象的所有权,那么这还是可能会导致致命的执行期错误。

    结论:const reference是智能指针作为参数传递的底线(const auto_ptr<int> &p)

    7. auto_ptr不能初始化为指向非动态内存

    原因很简单,delete 表达式会被应用在不是动态分配的指针上这将导致未定义的程序行为。

    8. auto_ptr常用的成员函数

    1) get()

    返回auto_ptr指向的那个对象的内存地址。如下例:

    int* p = new int(33);

    cout << "the adress of p: "<< p << endl;

    auto_ptr<int> ap1(p);

    cout << "the adress of ap1: " << &ap1 << endl;

    cout << "the adress of the object which ap1 point to: " << ap1.get() << endl;

    输出如下:

    the adress of p: 00481E00

    the adress of ap1: 0012FF68

    the adress of the object which ap1 point to: 00481E00

    第一行与第三行相同,都是int所在的那块内存的地址。第二行是ap1这个类对象本身所在内存的地址。

    2) reset()

    重新设置auto_ptr指向的对象。类似于赋值操作,但赋值操作不允许将一个普通指针指直接赋给auto_ptr,而reset()允许。如下例:

    auto_ptr< string > pstr_auto( new string( "Brontosaurus" ) );

    pstr_auto.reset( new string( "Long -neck" ) );

    在例子中,重置前pstr_auto拥有"Brontosaurus"字符内存的所有权,这块内存首先会被释放。之后pstr_auto再拥有"Long -neck"字符内存的所有权。

    这种做法可以避免出现利用已经存在的智能指针来构造新的智能指针而造成的问题(上面)。

    注:reset(0)可以释放对象,销毁内存。

    3) release()

    返回auto_ptr指向的那个对象的内存地址,并释放对这个对象的所有权。

    用此函数初始化auto_ptr时可以避免两个auto_ptr对象拥有同一个对象的情况(与get函数相比)。

    例子如下:

    auto_ptr< string > pstr_auto( new string( "Brontosaurus" ) );

    auto_ptr< string > pstr_auto2( pstr_auto.get() ); //这是两个auto_ptr拥有同一个对象

    auto_ptr< string > pstr_auto2( pstr_auto.release() ); //release可以首先释放所有权

    总结:目前新标准的C++已近摒弃了auto_ptr,一块动态内存只能由一个auto_ptr智能指向;一主要原因就是当出现已存在的两个智能指针之间相互赋值或者拷贝时,再次操作原来的auto_ptr智能指针时,会出错。

    参考:http://blog.sina.com.cn/s/blog_7708265a01010lyv.html

       https://www.cnblogs.com/lanxuezaipiao/p/4132096.html

  • 相关阅读:
    连续3年!SpreadJS 纯前端表格控件荣获“中国优秀软件产品”
    终于有一款组件可以全面超越Apache POI
    List<Object> 多条件去重
    xml文档的解析并通过工具类实现java实体类的映射:XML工具-XmlUtil
    soap get/post请求
    map转java对象
    springboot postman 对象里传时间格式问题
    spring boot的多环境部署
    Hibernate 之 @Query查询
    利用maven命令将外部jar包导进maven仓库
  • 原文地址:https://www.cnblogs.com/xuelisheng/p/9349308.html
Copyright © 2020-2023  润新知