boost库学习①:智能指针

  • 智能指针类型

  • 什么叫智能指针?

        智能指针是利用RAII(在对象的构造函数中执行资源的获取(指针的初始化),在析构函数中释放(delete 指针):这种技法把它称之为RAII(Resource Acquisition Is Initialization:资源获取即初始化))来管理资源。

        其本质思想是:将堆对象的生存期用栈对象(智能指针)来管理。也就是当new一个堆对象的时候,立刻用智能指针来接管,具体做法是在构造函数中进行初始化(用一个指针指向堆对象),在析构函数调用delete来释放堆对象。由于智能指针本身是一个栈对象,它的作用域结束的时候会自动调用析构函数,从而调用delete释放了堆对象。

        这样,堆对象就由智能指针(栈对象)管理了,不容易发生内存泄露或者是野指针。

C++98时期的智能指针auto_ptr已经被淘汰了。

C++11/14标准中出现了unique_ptrshared_ptrweak_ptr,源于boost库中的scoped_ptrshared_ptrweak_ptr(boost中共有6种智能指针)。

  • scoped_ptr(scoped_array)

       scoped_ptr包装了new在堆上的动态对象,能保证对象能在任何时候都被正确的删除。该智能指针只可以在本作用域内使用,其所有权不希望被转让,所以将拷贝构造函数,以及赋值重载函数包装进private中。scoped_arrayscoped_ptr的唯一不同是scoped_array管理数组对象。不建议使用scoped_array,可用vector替代。

部分scpoed_ptr代码如下:

template<class T> class scoped_ptr // noncopyable
{
private:

    T * px;//原始指针
    //禁止对智能指针的拷贝,保证被它管理的指针不被转让所有权。
    //如果一个类持有scoped_ptr成员,它也是禁止拷贝赋值的。
    scoped_ptr(scoped_ptr const &);//拷贝构造私有化
    scoped_ptr & operator=(scoped_ptr const &);//赋值操作私有化

    typedef scoped_ptr<T> this_type;

    void operator==( scoped_ptr const& ) const;
    void operator!=( scoped_ptr const& ) const;

public:

    typedef T element_type;

    explicit scoped_ptr( T * p = 0 ); // never throws


    explicit scoped_ptr( std::auto_ptr<T> p );

    ~scoped_ptr(); // never throws

    void reset(T * p = 0); // never throws,重置指针,一般不用
    //模拟原始指针操作,保存空指针时2操作未定义
    T & operator*() const; // never throws
    T * operator->() const; // never throws

    T * get() const;//获得原始指针

    void swap(scoped_ptr & b);//交换指针
};

使用如下:

#include <boost/scoped_ptr.hpp>
#include <iostream>
using namespace std;
 
class X
{
public:
    X()  { cout << "X ..." << endl;}
    ~X() { cout << "~X ..." << endl;}
};
 
int main(void){
    cout << "Entering main ..." << endl;
    {
        boost::scoped_ptr<x> pp(new X);
        //boost::scoped_ptr<x> p2(pp); //Error:所有权不能转移
    }
    cout << "Exiting main ..." << endl;
    return 0;
}

上述代码中的main函数,首先通过new来进行类X的构造,调用X的构造函数,然后将其对象传入智能指针中,在该指针作用域结束后,进行该指针的自动释放,即自动调用析构函数(作用域为其中的构造代码块,即俩花括号之间)。

扫描二维码关注公众号,回复: 8867091 查看本文章

同时,可以通过reset()来释放堆对象,其实现是通过交换来实现的。

void reset(T *p = 0){  // never throws
    BOOST_ASSERT( p == 0 || p != px ); // catch self-reset errors
    this_type(p).swap(*this);
}

void swap(scoped_ptr &b){  // never throws
    T *tmp = b.px;
    b.px = px;
    px = tmp;
}

定义typedef scoped_ptr this_type, 当调用pp.reset()reset 函数构造一个临时对象,它的成员px=0, 在swap 函数中调换 pp.px(this_type)(p).px, 即现在智能指针pp.px = 0(解绑),临时对象接管了原生指针(即所有权可以交换),reset 函数返回,离开了函数作用域,栈上的临时对象析构,调用析构函数,进而delete px。

  • shared_ptr和weak_ptr

  •  shared_ptr

       shared_ptr是最像指针的智能指针。shared_ptrscoped_ptr一样包装了new在堆上的动态对象,也不可以管理new[]产生的数组指针,也没有指针算术操作。但是shared_ptr实现了引用计数,可以自由拷贝赋值,在任意地方共享它,当没有代码使用它(引用计数为0)时,它才删除被包装的动态对象。shared_ptr可以被放在标准容器中,STL容器存储指针的标准做法。

补充:

       shared_ptr消除了显示调用delete,但是没有消除显示调用newboost提供一个工厂函数make_shared来消除显式new调用。例子如下:

void fun()
{
    auto sp = boost::make_shared<string>("make_shared");//创建string共享指针
    auto spv = boost::make_shared< vector<int> >(10,2);//vector共享指针
}

下面几条规则能使我们更加安全的使用boost::shared_ptr:

  1. 避免对shared_ptr所管理的对象的直接内存管理操作,以免造成该对象的重释放;
  2. shared_ptr并不能对循环引用的对象内存自动管理(这点是其它各种引用计数管理内存方式的通病);
  3. 不要构造一个临时的shared_ptr作为函数的参数;
  • weak_ptr

        weak_ptr是为了配合shared_ptr而引入的,它没有普通指针的行为,没重载*和->。它最大的作用是协助shared_ptr工作,像旁观者一样观测资源的使用情况。weak_ptr可以从一个shared_ptr或者一个weak_ptr对象构造,获得对象资源观测权。但是weak_ptr并没有共享资源,它的构造不会引起引用计数的增加,也不会让引用计数减少,它只是静静的观察者。

部分代码如下:

template<class T> class weak_ptr
{
private:
    typedef weak_ptr<T> this_type;

public:
    weak_ptr();

    ~weak_ptr();

    weak_ptr( weak_ptr const & r );

    weak_ptr & operator=( weak_ptr const & r );

    weak_ptr( weak_ptr<Y> const & r );

    shared_ptr<T> lock() const;//获取shared_ptr,把弱关系转为强关系,操作资源

    long use_count() const;//引用计数

    bool expired() const;//是否失效指针

    void reset();//重置指针

    void swap(this_type & other);

    element_type * px;            // contained pointer
    boost::detail::weak_count pn; // reference counter

};  // weak_ptr

template<class T, class U> inline bool operator<(weak_ptr<T> const & a, weak_ptr<U> const & b);
template<class T> void swap(weak_ptr<T> & a, weak_ptr<T> & b);

weak_ptr的引入主要是为了解决shared_ptr的循环引用的问题:

class node
{
    public:
    ~node(){cout<<"deleted.\n";}
    typedef shared_ptr<node> ptr_type;
    ptr_type next;
};

int main()
{
    auto p1 = make_shared<node>();
    auto p2 = make_shared<node>();

    p1->next = p2;//形成循环链表
    p2->next = p1;

    cout<<p1->use_count()<<endl;//=2
    cout<<p2->use_count()<<endl;//=2
    return 0;
}

两个node对象都互相持有对方的引用,每个shared_ptr对象的引用计数都是2,因此在析构时不能减为0,不会调用删除操作,导致内存泄漏。这个时候我们可以使用weak_ptr,因为它不会增加引用计数,这强引用变为弱引用,在可能循环的地方打破循环,真正需要shared_ptr的时候调用weak_ptrlock()函数。

其中一种解决循环引用问题的办法是手动打破循环引用,如在return 0; 之前加上一句p1->next.reset(),循环引用即被打破。但是手动干预已经违背了我们使用智能指针的初衷。

另外一种解决办法就是使用weak_ptr智能指针,即:

//修改以上
    typedef weak_ptr<node> ptr_type;

    p1->next = p2;//形成循环链表
    p2->next = p1;//weak_ptr所以正常

    if (!p1->next->expired()){      //检查弱引用是否有效
        auto p3 = p1->next->lock(); //获得强引用
    }

    //退出后,shared_ptr都正确析构

 

  • 参考

Boost库——四种智能指针的对比和注意点

详解Boost库智能指针

发布了90 篇原创文章 · 获赞 6 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_37160123/article/details/93616841