zoukankan      html  css  js  c++  java
  • boost-智能指针

     使用boost的智能指针需要包含头文件"boost/smart_ptr.hpp",c++11中需要包含头文件<memory>

    1、auto_ptr、scoped_ptr、scoped_array

      ①、auto_ptr是C++标准中的智能指针,在指针退出作用域的时候自动释放指针指向的内存,即使是异常退出的时候。auto_ptr实际上是一个对象,重载了operator*和operator->,且提供了一些成员函数,比如使用成员get()可以获得对应类型的原始指针。

      auto_pt的特点是可以对其进行复制和赋值,但同一时刻只能有一个auto_ptr管理指针。

      使用之前需要包含头文件<memory>,eg:

    #include <memory>
    #include <cassert>
    int main()
    {
        std::auto_ptr<int> ap1(new int(5));
        cout << *ap1 << endl;
    
        int* p = ap1.get();
        cout << *p << endl;
    
        std::auto_ptr<int> ap2(ap1);//ap1失去管理权,不再拥有指针,ap2得到管理权
        assert(ap1.get() == 0);//get()获得的指针为空
    
        return 0;
    }
    View Code

      ②、boost的scoped_ptr用法类似于auto_ptr,都不能用作容器的元素,不支持++、--等指针算数操作。

       scoped_ptr的特点是拷贝构造函数和赋值操作符都是私有的,所以scoped_ptr不能进行复制和赋值操作,保证了对对象的唯一管理权。

    #include "boost/smart_ptr.hpp"
    
    int main()
    {
        boost::scoped_ptr<int> sp1(new int(10));
        boost::scoped_ptr<int> sp2(sp1);//编译无法通过:不能转让sp1的管理权到sp2
        boost::scoped_ptr<int> sp3(new int(30));
        sp3 = sp1;//编译无法通过:不能转让sp1的管理权到sp3
    
        return 0;
    }
    View Code

      ③、如果需要指向new[]开辟的内存数组,应该使用scoped_array而不是scoped_ptr。scoped_array构造函数的参数必须是new[]返回的指针,支持使用[]来访问元素,但不支持*、->运算符。scoped_array也不支持拷贝,赋值。

    2、shared_ptr、shared_array、weak_ptr

      ①、概述

      shared_ptr没有scoped_ptr的限制,它可以被自由的拷贝和赋值,也可以作为容器的元素。它是一种引用计数型的智能指针,当没有代码使用它时,即引用计数为0的时候自动删除动态分配的内存。

      shared_ptr也支持*和->操作符,支持==比较操作(相当于a.get() == b.get()),提供隐式bool类型转换以判断指针的有效性,同样不提供指针的算数运算。

      shared_ptr中的一些成员函数:

      get():获取内部原始指针。

      reset():重置shared_ptr,会导致引用计数减1。不带参数的reset()将shared_ptr重置为不指向任何对象,带参数的reset()用来将shared_ptr重置为指向新的对象。

      unique():用来检测当前是不是指针的唯一管理者,即引用计数为1。

      use_count():用来获取当前引用计数,但它一般在调试中使用,因为其效率很低,如果只是判断当前引用计数是否为1的话可以使用unique()来替换它。

      将shared_ptr赋值为nullptr相当于调用reset()。

      shared_ptr也提供了转换运算符dynamic_pointer_cast<>、static_pointer_cast<>、const_pointer_cast<>。

      使用示例1:

    #include "boost/smart_ptr.hpp"
    
    int main()
    {
        boost::shared_ptr<int> spInt(new int(10));
        if (spInt) //true
        {
            spInt.reset();
            if (spInt) //false
                ;
        }
    
        boost::shared_ptr<int> spInt1(new int(5)); //指针引用计数加1,现为1
        assert(spInt1.unique());
    
        boost::shared_ptr<int> spInt2(spInt1); //指针的引用计数加1,现为2
    
        boost::shared_ptr<int> spInt3;
        assert(!spInt3);
        spInt3 = spInt1; //指针的引用计数加1,现为3
    
        spInt3.reset(); //spInt3被重置,指针的引用计数减1,现为2
        assert(!spInt3);
    
        return 0; //spInt2退出作用域,指针引用计数减1,为1, spInt1退出作用域,指针引用计数减1,为0,此时释放内存
    }
    View Code

      使用示例2:

    class Foo;
    typedef std::shared_ptr<Foo> SPtr;
    
    class Foo
    {
    };
    
    void func(SPtr sp)
    {
        int cnt = sp.use_count();
        cout << cnt << endl;
    }
    
    int main()
    {    
        func(SPtr(new Foo)); //输出为1
    
        SPtr sp(new Foo); 
        func(sp); //输出为2
    
        return 0;
    }
    View Code

     ②、注意事项

      shared_ptr不支持直接使用原始指针进行"="操作:

        int*p = new int(0);
        std::shared_ptr<int> sp(p);
        //std::shared_ptr<int> sp = p; //error !
        //sp = p; //error !
    View Code

      对shared_ptr进行 = 操作后原shared_ptr的引用计数会减1,如果变为0的话就会释放其指向的对象,但原对象的释放是在新对象生成之后,稳妥的方法是=操作之前先进行reset操作:

        auto sp = std::make_shared<CFoo>();
    
        sp.reset(); //先使用reset()或= nullptr来释放原对象
        sp = std::make_shared<CFoo>();
    View Code

      shared_ptr不能多次引用同一原始指针,否则会产生多次释放错误:

        CFoo* p = new CFoo;
        std::shared_ptr<CFoo> sp(p);
        std::shared_ptr<CFoo> sp2(p); //error !
    View Code

      shared_ptr不能直接包含对象的this指针,如下所示的代码同样会导致两次释放问题:

    class Foo
    {
    public:
        virtual ~Foo()
        {
            int a = 0;
        }
        std::shared_ptr<Foo> getSharedPtr()
        {
            return std::shared_ptr<Foo>(this);
        }
    };
    
    int main()
    {    
        std::shared_ptr<Foo> sp(new Foo);
        std::shared_ptr<Foo> spT = sp->getSharedPtr();
    
        return 0;
    }
    View Code

      应该使用shared_from_this,需要注意的两点:shared_from_this()方法不能在当前类的构造函数中调用,如果当前对象由一个unique_ptr控制生命,那么对象内不能使用shared_from_this:

    class Foo : public std::enable_shared_from_this<Foo>
        {
        public:
            virtual ~Foo()
            {
                int a = 0;
            }
            std::shared_ptr<Foo> getSharedPtr()
            {
                return shared_from_this();
            }
        };
    
        int main()
        {
            std::shared_ptr<Foo> sp(new Foo);
            std::shared_ptr<Foo> spT = sp->getSharedPtr();
    
            return 0;
        }
    View Code

      shared_ptr不是线程安全的,它的的线程安全级别和内置类型、容器、string 一样,即:

       l 多个线程对不同的shared_ptr写入是线程安全的,即使这些shared_ptr指向同一原始指针。
       l 同一个 shared_ptr 可被多个线程同时读取。
       l 如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁。

      如下所示:

    shared_ptr<Foo> globalPtr;
    Mutex mutex;
    
    void read()
    {
        shared_ptr<Foo> ptr;
        {
            MutexLock lock(mutex); //RAII Mutex
            ptr = globalPtr;  // read globalPtr
        }
    
        // use ptr since here
        doit(ptr);
    }
    
    void write()
    {
        shared_ptr<Foo> newptr(new Foo);
        {
            MutexLock lock(mutex); //RAII Mutex
            globalPtr = newptr;  // write to globalPtr
        }
    
        // use newptr since here
        doit(newptr);
    }
    View Code

      应该避免使用临时的shared_ptr对象,对此,boost上的说明是:假设有下面的代码,ok函数是正确的做法,而bad函数有可能会导致内存泄露,因为f函数参数的执行顺序可能是先new int(2),再执行g(),然后再执行shared_ptr<int>(),假设在g()方法中产生了异常,将不会再执行shared_ptr<int>(),new int(2)的内存泄露。

    void f(shared_ptr<int>, int);
    int g();
    
    void ok()
    {
        shared_ptr<int> p(new int(2));
        f(p, g());
    }
    
    void bad()
    {
        f(shared_ptr<int>(new int(2)), g());
    }
    View Code

     ③、工厂函数make_shared

      当shared_ptr的构造参数是一个new操作符的时候,虽然我们不用手动调用delete来释放它,可这导致了代码中的某种不对称性,所以应该使用工厂模式来解决:头文件"boost/make_shared.hpp"中提供了一个自由工厂函数make_shared<T>()来消除显示的new操作,它可以返回一个shared_ptr<T>对象,使用示例:

    #include "boost/smart_ptr.hpp"
    #include "boost/make_shared.hpp"
    #include <vector>
    
    int main()
    {
        boost::shared_ptr<string> sp = boost::make_shared<string>("make_shared");
        boost::shared_ptr<std::vector<int>> spv = boost::make_shared<std::vector<int>>(10, 2);
        assert(spv->size() == 10);
    
        return 0;
    }
    View Code

     C++11中也提供了std::make_shared<T>()来使用。

     ④、删除器

      shared_ptr默认使用delete释放它指向的对象,我们可以通过使用其另一种构造方法来指定其他的释放操作。

      shared_ptr有一种特殊形式的构造函数:shared_ptr(T* p, D d); 这里边的d就是删除器,它可以是一个函数对象或函数指针。删除器用来指定shared_ptr在析构的时候即离开作用域的时候不是执行释放内存的操作,而是执行d函数。函数get_deleter()可以获得删除器指针。

      删除器使用示例1:

    std::unique_ptr<int> u5 (new int, std::default_delete<int>());

      删除器使用示例2:

    class CSock
    {
        ...
    };
    
    CSock* open_sock()
    {
        CSock* s = new CSock;
        ...//do some open job
        
        return p;
    }
    
    void close_sock(CSock* s)
    {
        ...//do some close job
        delete s;
    }
    
    boost::shared_ptr(CSock*)(open_sock(), close_sock);
    View Code

      删除器使用示例3:

    struct Foo {
        Foo() { std::cout << "Foo...
    "; }
        ~Foo() { std::cout << "~Foo...
    "; }
    };
    
    struct D {
        void operator()(Foo* p) const {
            std::cout << "Call delete from function object...
    ";
            delete p;
        }
    };
    
    std::shared_ptr<Foo> sh4(new Foo, D());
    
    std::shared_ptr<Foo> sh5(new Foo, [](auto p) {
        std::cout << "Call delete from lambda...
    ";
        delete p;
    });
    View Code

      删除器使用示例4:

    //当shared_ptr离开作用域的时候,自动调用fclose()关闭文件。
    shared_ptr<FILE> fp(fopen("./1.txt", "r"), fclose);
    View Code

      ⑤、shared_array

      使用shared_array来指向使用new[]开辟的数组,它同样使用引用计数机制。

      ⑥、weak_ptr

      我们一般使用一个shared_ptr或weak_ptr来初始化weak_ptr,当使用一个shared_ptr对象来构造weak_ptr的时候不会引起shared_ptr引用计数的增加,即weak_ptr不能控制对象的生命期。

      weak_ptr主要用来获悉对象是存在的还是已经被释放了,通过成员函数lock()。lock()可以从被观测的shared_ptr获得一个shared_ptr对象,如果对象存在的话,lock()会导致shared_ptr的引用计数加1,如果对象已经被释放了则lock()返回空的shared_ptr(以默认构造函数构造的shared_ptr,其use_count()为0)。lock()是线程安全的。weak_ptr的expired()方法可以用来判断当前weak_ptr对象是否是空的,当weak_ptr没有观测对象(没有用shared_ptr进行初始化)或者观测对象已经被释放expired()返回true。weak_ptr也有use_count()方法,它获得的是被观测对象(shared_ptr)的引用计数。

        boost::weak_ptr<int> wpEmpty;
        bool b = wpEmpty.expired(); //true
    
        boost::shared_ptr<int> sp(new int(10));
        boost::weak_ptr<int> wp(sp);
        b = wp.expired(); //false
    
        boost::shared_ptr<int> sp2 = wp.lock();
        if (sp2)
        {
            assert(wp.use_count() == 2);
        }
    
        sp.reset();
        sp2.reset();
        b = wp.expired(); //true
    View Code

       当两个shared_ptr互相包含的时候,会导致双方无法释放的问题,比如下面这种情况,当func()返回的时候两个对象都得不到释放,解决方法是使用weak_ptr来代替shared_ptr。

    class Child;
    class Parent
    {
    public:
        ~Parent() { cout << "Parent destruct" << endl; }
        shared_ptr<Child> m_spChild;
    };
    
    class Child
    {
    public:
        ~Child() { cout << "Child destruct" << endl; }
        shared_ptr<Parent> m_spParent;
    };
    
    void Func()
    {
        shared_ptr<Parent> spParent(new Parent);
        shared_ptr<Child> spChild(new Child);
        spParent->m_spChild = spChild;
        spChild->m_spParent = spParent;
    }
    
    int main()
    {
        Func();
    
        getchar();
        return 0; 
    }
    View Code

     3、unique_ptr

      unique_ptr与shared_ptr不同的是只能有一个unique_ptr指向一个对象,因此unique_ptr不支持普通的拷贝或赋值操作(使用工厂函数make_unique<>进行 = 初始化除外)。一般情况下不推荐使用unique_ptr,因为在一些特定的复杂情况下unique_ptr的特性会导致内存不能复制而编译出错:

        std::unique_ptr<string> p1(new string("test"));
        std::unique_ptr<string> p2 = std::make_unique<string>("hello");
        p2 = std::make_unique<string>("abc");
    
        p2 = p1; //error
        std::unique_ptr<string> p3(p1); //error
        std::unique_ptr<string> p4 = p1; //error

       成员方法get()可以获得当前管理对象的指针,release()或reset()将指针的所有权从一个unique_ptr转移到另一个unique_ptr。release()会返回当前保存的指针,而且它仅仅是切断与当前管理对象的关系,并不会释放该对象资源。reset()接收一个新的指针,并将原来管理的对象释放。不能拷贝unique_ptr的规则有一个例外,我们可以拷贝一个将要被销毁的unique_ptr,最常见的例子是从函数返回一个unique_ptr:

    std::unique_ptr<string> sp1;
    sp1.reset(new string); //使用reset初始化
    
    std::unique_ptr<string> sp2;
    sp2 = std::unique_ptr<string>(new string); //使用临时的unique_ptr赋值
    
    std::unique_ptr<int> clone(int num)
    {
        //返回临时unique_ptr
        return std::unique_ptr<int>(new int(num));
    }
    
    std::unique_ptr<int> clone(int num)
    {
        std::unique_ptr<int> ret(new int(num));
        return ret; //返回局部unique_ptr
    }
    View Code

       当我们使用容器保存unique_ptr的时候应该使用临时的unique_ptr或std::move():

        std::vector<std::unique_ptr<int>> vc;
    
        //使用临时的unique_ptr
        vc.push_back(std::make_unique<int>(100));
        
        //使用std::move()
        std::unique_ptr<int> up = std::make_unique<int>(100);
        vc.push_back(std::move(up));
    View Code

      类似shared_ptr,unique_ptr也可以指定删除器,但它与管理删除器的方式与shared_ptr有所不同。

  • 相关阅读:
    【 React
    vue : 无法加载文件 C:UsersXXXAppDataRoaming pmvue.ps1,因为在此系统上禁止运行脚本
    web前端工程化
    node.js读写文件
    gulp简单使用
    在window里面安装ubuntu子系统并安装图形化界面
    节点操作--JavaScript
    【jQuery中css(),attr()和prop区别】
    【animation和transtion】
    【网络状态反馈码】
  • 原文地址:https://www.cnblogs.com/milanleon/p/7454231.html
Copyright © 2011-2022 走看看