• 智能指针(转载)


    https://www.jianshu.com/p/e4919f1c3a28

    什么是智能指针?

    智能指针是一个RAII(Resource Acquisition is initialization资源获取即初始化)类模型,用于动态分配内存。它提供所有普遍指针提供的接口,却很少发生异常。在构造是,它分配内存,当离开作用域时,它会自动释放已分配的内存。这样,程序员就从手动管理动态内存的繁杂任务重解放出来。

    C++98提供第一种智能指针:auto_ptr。

    auto_ptr:

    class Test {
        public:
        Test(int a = 0):m_a(a){}
        ~Test() {
            cout << "calling destructor" << ends;
        }
    };
    
    void main() {
        std::auto_ptr<Test> p(new Test(5));
        cout << p->m_a << endl;
    }

    上述代码会智能地释放与指针绑定的内存。作用的过程是这样的:我们申请了一块内存来放Test对象,并且把它绑定到auto_ptr p上。当p离开作用域时,它所指向的内存块也会被自动释放。

    存在问题1:

    当把一个auto_ptr赋给另一个auto_ptr时,它的所有权也转移了。当我在函数间传递auto_ptr时,这就是个问题。我在Foo()中有一个auto_ptr,然后在Foo()中把指针传递给了Fun()函数,当Fun()函数执行完毕时,指针的所有权不会再返还给Foo。

    class Test {
    public:
        Test(int a = 0):m_a(a) {}
        ~Test() {
            cout << "calling destructor" << ends;
        }
    
    public:
        int m_a;
    };
    
    void Fun(auto_ptr<Test> p1) {
        cout << p1->m_a << endl;
    }
    
    void main() {
        auto_ptr<Test> p(new Test(5));
        Fun(p);
        cout << p->m_a << endl;
    }

    由于auto_ptr的野指针行为,导致上述代码程序崩溃。过程如下:p拥有一块内存,当Fun调用时,p把关联的内存块的所有权传给auto_ptr p1,p1是p的copy(注:这里从Fun函数的定义式看出,函数参数时值传递,所有把p的值拷进函数中,这个时候p中已经没有内容了),这时p1就拥有了之前p拥有的内存块。在Fun函数执行完了,p1离开了作用域,所以p1关联的内存块也就释放了。此时进行main函数,p中什么都没有,这是程序崩溃的原因。下一行还试图访问p,但是p中没有拥有资源。

    存在问题2:

    auto_ptr不能指向一组对象,也就是说不能和操作符new[]一起使用。

    void main() {
      std::auto_ptr<Test> p(new Test[5]);
    }

    上面代码将产生一个运行时错误。因为auto_ptr离开作用域时,delete被默认使用释放关联内存空间。如果创建的是一组对象,应该使用delete[]释放,而不是delete。

    存在问题3:

    auto_ptr不能和标准容器(vector、list、map......)一起使用。

    由于auto_ptr容易产生错误,所以它也将被废弃。

    C++11提供一组新的智能指针:shared_ptr、weak_ptr、unique_ptr。

    shared_ptr:

    shared_ptr有一个共享所有权(shared ownership)的概念。shared_ptr的目标非常简单:多个指针可以同时指向一个对象,当最后一个shared_ptr离开作用域时,内存才会释放。

    创建:

    void main() {
        shared_ptr<int> sptr1(new int);
    }

    使用make_shared宏来加速创建的过程。因为shared_ptr主动分配内存并且保存引用计数(reference count),make_shared以一种更有效率的方法来实现创建工作。

    void main() {
        shared_ptr<int> sptr2 = make_shared<int>(100);
    }

    上面的代码创建了一个shared_ptr,指向一块内存,该内存包含一个整数100,以及引用计数1。如何通过sptr2再创建一个shared_ptr,引用计数就会变成2。该计数被称为强引用(strong reference),除此之外,shared_ptr 还有另外一种引用计数叫做弱引用(weak reference)。

    通过调用use_count()可以得到引用计数,据此你能找到shared_ptr的数量。当debug时,可以通过观察shared_ptr中strong_ref的值得到引用计数。

    析构:

    shared_ptr默认调用delete释放关联资源。如果用户采用一个不一样的析构策略时,可以指定构造这个shared_ptr的策略。下面的例子是由于采用默认析构策略导致的问题。

    class Test {
    public:
        Test(int a=0) : m_a(0){}
        ~Test() {
            cout << "calling destructor" << ends;
        }
    public:
        int m_a;
    }
    
    void main() {
        shared_ptr<Test> sptr1(new Test[5]);
    }

    在此场景下,shared_ptr指向一组对象,但是当离开作用域时,默认的析构函数调用delete释放资源。实际上,我们应该调用delete[]来销毁这个数组。用户可以通过调用一个函数,例如一个lamda表达式,来指定一个通用的释放步骤。

    void mian() {
        shared_ptr<Test> sptr1(new Test[5], [](Test* p){ delete[] p;});
    }

    接口:

    就像一个普通指针一样,shared_ptr也提供解引用操作符*,->。除此之外,它还提供一些更重要的接口。

    get():获取shared_ptr绑定的资源。

    reset():释放关联内存块的所有权,如果是最后一个指向该资源的shared_ptr,就释放这块内存。

    unique():判断是否是唯一指向当前内存的shared_ptr。

    operator bool:判断当前的shared_ptr是否指向一个内存块。可以用if表达式判断。

    存在问题1:

    void main() {
        int* p = new int;
        shared_ptr<int> sptr1(p);
        shared_ptr<int> sptr2(p);
    }

    上述代码会产生一个错误,因为两个来自不同组的shared_ptr指向同一个资源。在析构的时候会出现p指针 double free。

    避免这个问题,尽量不要从一个裸指针(naked pointer)创建shared_ptr。但是可以使用new的方式进行创建。

    存在问题2:  循环引用问题

    class B;
    class A {
    public:
        A() : m_sptrB(nullptr){}
        ~A(){
            cout << "this is A destroyed" << ends;
        }
        shared_ptr<B> m_sptrB;
    };
    
    class B {
        B(): m_sptrA(nullptr) {}
        ~B {
            cout << "this is B destroyed" << endl;
        }
        shared_ptr<A> m_sptrA;
    };
    
    void main() {
        shared_ptr<B> sptrB(new B);
        shared_ptr<A> sptrA(new A);
        
        sptrB->m_sptrA = sptrA;
        sptrA->m_sptrB = sptrB;
    }

    A和B有一个shared_ptr,B对A也有一个shared_ptr,与sptrA和sptrB关联的资源没有被释放,参考下表:

    当sptrA和sptrB离开作用域时,它们的引用计数都只减少到1,所以它们指向的资源并没有释放。

    1、如果几个shared_ptr指向的内存块属于不同组,将产生错误。

    2、如果从一个普通指针创建一个shared_ptr还会引发另外一个问题。在上面代码中,考虑到只有一个shared_ptr是由p创建的,代码可以正常工作。如果程序在智能指针作用域结束之前删除普通指针p,会出现崩溃。

    3、循环引用:如果共享智能指针卷入循环引用,资源不会正常释放。

    为了解决循环引用,C++提供了weak_ptr。

    weak_ptr:

    weak_ptr拥有共享语义,和不包含语义。说明weak_ptr可以共享shared_ptr持有的资源。所以可以从一个包含资源的shared_ptr创建weak_ptr。

    weak_ptr不支持普通指针包含的*,->操作。它并不包含资源也不允许程序员操作资源。既然如此,如何使用weak_ptr?

    答案是从weak_ptr中创建shared_ptr,然后再使用它。通过增加强引用计数,当使用时可以确保资源不会被销毁。当引用计数增加时,可以肯定的是从weak_ptr中创建的shared_ptr引用计数至少为1.否则,当使用weak_ptr就可能发生如下问题:当shared_ptr离开作用域时,其拥有的资源会释放,从而导致混乱。

    创建:

    以shared_ptr作为参数构造weak_ptr。从shared_ptr创建weak_ptr增加了共享指针的弱引用计数(weak reference),意味着shared_ptr与其他指针共享着它所拥有的资源。但是当shared_ptr离开作用域时,这个计数不作为是否释放资源的依据。换句话说,就是除非强引用计数变为0,才会释放指针指向的资源,在这里弱引用计数不起作用。

    void main() {
        shared_ptr<Test> sptr(new Test);
        weak_ptr<Test> wptr(sptr);
        weak_ptr<Test> wptr1 = ptr;
    }

    shared_ptr和weak_ptr的引用计数如下:

    将weak_ptr赋给另一个weak_ptr会增加弱引用计数。

    当shared_ptr离开作用域时,其内的资源释放了,这时指向该shared_ptr的weak_ptr会变成过期(expired)。

    如何判断weak_ptr是否指向有效资源,有两种方法:

    1、调用use_count()获取引用计数,该方法只返回强引用计数,不返回弱引用计数。

    2、调用expired()方法。比调用use_count()方法更快。

    从weak_ptr调用lock()可以得到shared_ptr,或者直接将weak_ptr转型为shared_ptr。

    void main() {
        shared_ptr<Test> sptr(new Test);
        weak_ptr<Test> wptr(ptr);
        shared_ptr<Test> sptr2 = wptr.lock();
    }

    如之前所述,从weak_ptr中获取shared_ptr增加强引用计数。

    weak_ptr解决循环引用问题。

    class B;
    class A {
    public:
        A():m_a(5) {};
        ~A() {
        }
    weak_ptr<B> m_wptrB;
    int m_a;
    }
    
    class  B {
         B():m_b(10){}
        ~B(){}
     weak_ptr<A> m_wptrA;
    int m_b;
    }
    
    void main() {
    shared_ptr<A> sptrA(new A);
    shared_ptr<B> sptrB(new B);
    sptrA->m_sptrB = sptrB;
    sptrB->m_sptrA = sptrA;
    }

    unique_ptr:

    unique_ptr是对auto_ptr的替换。unique_ptr遵循独占语义。在任何时间点,资源只能唯一的一个unique_ptr占有。当unique_ptr离开作用域,所包含资源被释放。如果资源被其他资源重写了,之前拥有的资源将被释放。所以保证关联的资源总是能被释放。

    创建:

    unique_ptr的创建方法和shared_ptr一样,除非创建指向数组类型的unique_ptr。

    unique_ptr<int> uptr(new int);

    unique_ptr提供了创建数组对象的特殊方法,当指针离开作用域时,调用delete[]代替delete。当创建unique_ptr时,这一组对象被视作模板参数的部分。这样,程序员就不需要再提供一个指定的析构方法。如下:

    unique_ptr<int[]> uptr(new int[5]);

    当把unique_ptr赋给另一个对象资源所有权就会被转移。另外,unique_ptr不提供复制语义(拷贝赋值和拷贝构造都不可以),只支持移动语义。

    unique_ptr提供的接口和传统指针差不多,但是不支持指针运算。

    unique_ptr提供一个release()方法,释放所有权。release和reset的区别在于,release仅仅释放所有权但是不释放资源,reset释放资源和所有权。

    如何选择?

    共享资源使用shared_ptr,独占资源使用unique_ptr。

    除此之外,shared_ptr比unique_ptr需要更多空间,因为需要做其他事情,比如存储强引用计数、弱引用计数。而unique_ptr不需要这些,只独占保存资源对象。

    shared_ptr的实现:

    实现的函数有:

    构造函数:参数为new T

    构造函数:参数为shared_ptr

    重载赋值操作符:shared_ptr<T> &operator = (const shared_ptr<T> &s)。注意需要先释放原shared_ptr的资源。

    重载*操作符:T& operator* () {return *(this->ptr)};

    重载->操作符:T* operator->() {return this->ptr};

    析构函数:注意需要释放资源。

    https://blog.csdn.net/xiaodu655/article/details/85780240

    auto_ptr和unique_ptr的区别:

    https://blog.csdn.net/crazy_father/article/details/46524709

  • 相关阅读:
    团队的最佳诠释
    诸葛烤鱼
    Agile Web Development with Rails 读书笔记 (四)
    Agile Web Development with Rails 读书笔记 (三)
    客户教育
    To Be Or Not To Be Is The Question
    [zz]记录一linux命令:xargs
    Ubuntu 12.04 开启 wubi 安装
    [zz]mknod用法以及主次设备号
    [zz]centos下安装KVM方法
  • 原文地址:https://www.cnblogs.com/xiaohaigegede/p/13975369.html
Copyright © 2020-2023  润新知