智能指针

智能指针和普通指针的区别在于智能指针实际上是对普通指针加了一层封装机制,这样的一层封装机制的目的是为了使得智能指针可以方便的管理一个对象的生命期。
智能指针主要是预防不当的析构行为,防止出现悬垂指针。
在C++中,我们知道,如果使用普通指针来创建一个指向某个对象的指针,那么在使用完这个对象之后我们需要自己删除它,例如:

ObjectType* temp_ptr = new ObjectType();
temp_ptr->foo();
delete temp_ptr;

很多材料上都会指出说如果程序员忘记在调用完temp_ptr之后删除temp_ptr,那么会造成一个悬挂指针(dangling pointer),也就是说这个指针现在指向的内存区域其内容程序员无法把握和控制,也可能非常容易造成内存泄漏。

可是事实上,不止是“忘记”,在上述的这一段程序中,如果foo()在运行时抛出异常,那么temp_ptr所指向的对象仍然不会被安全删除。

在这个时候,智能指针的出现实际上就是为了可以方便的控制对象的生命期,在智能指针中,一个对象什么时候和在什么条件下要被析构或者是删除是受智能指针本身决定的,用户并不需要管理。

根据具体的条件,我们一般会讨论这样几种智能指针,而如下所说的这些智能指针也都是在boost library里面定义的 

1) scoped_ptr:
这是比较简单的一种智能指针,正如其名字所述,scoped_ptr所指向的对象在作用域之外会自动得到析构

此外,scoped_ptr是non-copyable的,也就是说你不能去尝试复制一个scoped_ptr的内容到另外一个scoped_ptr中,这也是为了防止错误的多次析构同一个指针所指向的对象。

2) shared_ptr:
很多人理解的智能指针其实是shared_ptr这个范畴。

正如同学的答案所提到的,shared_ptr中所实现的本质是引用计数(reference counting),也就是说shared_ptr是支持复制的,复制一个shared_ptr的本质是对这个智能指针的引用次数加1,而当这个智能指针的引用次数降低到0的时候,该对象自动被析构,。

需要特别指出的是,如果shared_ptr所表征的引用关系中出现一个环,那么环上所述对象的引用次数都肯定不可能减为0那么也就不会被删除,为了解决这个问题引入了weak_ptr。

3) weak_ptr:
对weak_ptr起的作用,很多人有自己不同的理解,我理解的weak_ptr和shared_ptr的最大区别在于weak_ptr在指向一个对象的时候不会增加其引用计数,因此你可以用weak_ptr去指向一个对象并且在weak_ptr仍然指向这个对象的时候析构它,此时你再访问weak_ptr的时候,weak_ptr其实返回的会是一个空的shared_ptr。

实际上,通常shared_ptr内部实现的时候维护的就不是一个引用计数,而是两个引用计数,一个表示strong reference,也就是用shared_ptr进行复制的时候进行的计数,一个是weak reference,也就是用weak_ptr进行复制的时候的计数。weak_ptr本身并不会增加strong reference的值,而strong reference降低到0,对象被自动析构。

为什么要采取weak_ptr来解决刚才所述的环状引用的问题呢?需要注意的是环状引用的本质矛盾是不能通过任何程序设计语言的方式来打破的,为了解决环状引用,第一步首先得打破环,也就是得告诉C++,这个环上哪一个引用是最弱的,是可以被打破的,因此在一个环上只要把原来的某一个shared_ptr改成weak_ptr,实质上这个环就可以被打破了,原有的环状引用带来的无法析构的问题也就随之得到了解决。

4) intrusive_ptr:
简单的说,intrusive_ptr和shared_ptr的区别在于intrusive_ptr要求其所指向的对象本身实现一个引用计数机制,也就是说当对象本身包含一个reference counter的时候,可以使用intrusive_ptr。
*************************************************************************************************
 
 

 

#include <utility>
#include <iostream>
using namespace std;
 
class A
{
public:
    A() { id = ++count; cout << "create A" << id  <<  "\n"; }
    ~A() { cout << "destroy A" << id << "\n"; }
private:
    static int count;
    int id;
};
 
int A::count = 0;
 
/*调用该函数会丢失掉所有权*/
void sink(auto_ptr<A> a)
{
    cout << "Enter sink()\n";
}
 
/*调用该函数会创建对象,并获取所有权*/
auto_ptr<A> create()
{
    cout << "Enter create()\n";
    auto_ptr<A> a(new A());
    return a;
}
 
int main(int argc, char *argv[])
{
    auto_ptr<A> a1 = create();
    auto_ptr<A> a2 = a1; /*转移所有权,此时a1无效了*/
    auto_ptr<A> a3(new A());
    cout << "Exit create()\n";
    sink(a2);/*丢失所有权,会发现a2的释放在sink函数中进行*/
    cout << "Exit sink()\n";
    return 0;
}

 

 

 

auto_ptr浅析
auto_ptr是C++标准库中(<utility>)为了解决资源泄漏的问题提供的一个智能指针类模板(注意:这只是一种简单的智能指针)

auto_ptr的实现原理其实就是RAII,在构造的时候获取资源,在析构的时候释放资源,并进行相关指针操作的重载,使用起来就像普通的指针。

std::auto_ptr<ClassA> pa(new ClassA);

下面主要分析一下auto_ptr的几个要注意的地方:
1,Transfer of Ownership
auto_ptr与boost库中的share_ptr不同的,auto_ptr没有考虑引用计数,因此一个对象只能由一个auto_ptr所拥有,在给其他auto_ptr赋值的时候,会转移这种拥有关系。

2,从上可知由于在赋值,参数传递的时候会转移所有权,因此不要轻易进行此类操作。
比如:std::auto_ptr<ClassA> pa(new ClassA());
bad_print(pa); //丢失了所有权
pa->...; //Error
3,使用auto_ptr作为成员变量,以避免资源泄漏。

为了防止资源泄漏,我们通常在构造函数中申请,析构函数中释放,但是只有构造函数调用成功,析构函数才会被调用,换句话说,如果在构造函数中产生了异常,那么析构函数将不会调用,这样就会造成资源泄漏的隐患。
比如,如果该类有2个成员变量,指向两个资源,在构造函数中申请资源A成功,但申请资源B失败,则构造函数失败,那么析构函数不会被调用,那么资源A则泄漏。
为了解决这个问题,我们可以利用auto_ptr取代普通指针作为成员变量,这样首先调用成功的成员变量的构造函数肯定会调用其析构函数,那么就可以避免资源泄漏问题。
4,不要误用auto_ptr
1)auto_ptr不能共享所有权,即不要让两个auto_ptr指向同一个对象。
2)auto_ptr不能指向数组,因为auto_ptr在析构的时候只是调用delete,而数组应该要调用delete[]
3)auto_ptr只是一种简单的智能指针,如有特殊需求,需要使用其他智能指针,比如share_ptr。
4)auto_ptr不能作为容器对象,STL容器中的元素经常要支持拷贝,赋值等操作,在这过程中auto_ptr会传递所有权,那么source与sink元素之间就不等价了。

posted @ 2017-02-16 00:32  柳下_MBX  阅读(304)  评论(0编辑  收藏  举报