您的位置:首页 > 编程语言 > C语言/C++

智能指针学习

2016-09-13 10:44 51 查看
包含指针的类需要特别注意复制控制,原因是复制指针时只是复制了指针中的地址,而不会复制指针指向的对象!

    将一个指针复制到另一个指针时,两个指针指向同一对象。当两个指针指向同一对象时,可能使用任一指针改变基础对象。类似地,很可能一个指针删除了一对象时,另一指针的用户还认为基础对象仍然存在。指针成员默认具有与指针对象同样的行为。

大多数C++类采用以下三种方法之一管理指针成员:

    1)指针成员采取常规指针型行为:这样的类具有指针的所有缺陷但无需特殊的复制控制!

    2)类可以实现所谓的“智能指针”行为:通过对共享指针的对象进行计数,指针所指向的对象是共享的,但类能够防止悬垂指针。

    3)类采取值型行为:每个对象在建立的时候,不管是初始化或者复制构造,指针所指向的对象是唯一的,有每个类对象独立管理。
1 常规指针:

#ifndef _HASPTR_H_
#define _HASPTR_H_

/************************************************************************/
/*
*	带指针成员的简单类,其指针共享同一对象,因而会出现悬垂指针
*
*	date:2016/9/13
*
*/
/************************************************************************/

class HasPtr {
public:
HasPtr(int *p , int i):ptr(p) , val(i){};
~HasPtr(void){ }

public:
int *get_ptr() const { return ptr; }
int get_val() const { return val;}

void set_ptr( int *p ) { ptr =p;}
void set_val( int i) { val = i;}

int get_ptr_val() const { return *ptr;}
void set_ptr_val( int i) { *ptr = i;}

private:
int *ptr;
int val;
};

#endif

因为HasPtr类没有定义复制构造函数,所以复制一个HasPtr对象将复制两个成员,因为采取的常规的合成构造函数
#include <iostream>
#include "hasPtr.h"
using namespace std;

int main ()
{
int obj=0;

HasPtr ptr1(&obj,42);
//copy 后 &ptr1.ptr==&ptr2.ptr
cout<<ptr1.get_ptr()<<'\t'<<ptr1.get_ptr_val()<<'\t'<<ptr1.get_val()<<endl;

HasPtr ptr2(ptr1);
cout<<ptr2.get_ptr()<<'\t'<<ptr2.get_ptr_val()<<'\t'<<ptr2.get_val()<<endl;

//因为ptr2是ptr1  copy过来的,而set_ptr_val()函数仅仅修改的是*ptr的值
//ptr 的地址仍然不变,ptr1和ptr2还是相同
ptr1.set_ptr_val(34);
cout<<ptr1.get_ptr()<<'\t'<<ptr1.get_ptr_val()<<'\t'<<ptr1.get_val()<<endl;
cout<<ptr2.get_ptr()<<'\t'<<ptr2.get_ptr_val()<<'\t'<<ptr2.get_val()<<endl;

//改变了指针本身的值,因此两个指针所指向的内容不同
int m=44;
ptr1.set_ptr(&m);
cout<<ptr1.get_ptr()<<'\t'<<ptr1.get_ptr_val()<<'\t'<<ptr1.get_val()<<endl;
cout<<ptr2.get_ptr()<<'\t'<<ptr2.get_ptr_val()<<'\t'<<ptr2.get_val()<<endl;

////悬垂指针 ,因为ip和ptr中的指针指向同一对象,删除该对象,ptr中
//的指针不再指向有效对象了

//int *ip=new int (43);
//HasPtr ptr(ip,10);
//delete ip; //
//ptr.set_ptr_val(0);
//cout<<ptr.get_ptr()<<'\t'<<ptr.get_ptr_val()<<'\t'<<ptr.get_val()<<endl;

system("pause");
return 0;
}

2 智能指针

      智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露。它的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。
      智能指针就是模拟指针动作的类。所有的智能指针都会重载 -> 和 * 操作符。智能指针还有许多其他功能,比较有用的是自动销毁。这主要是利用栈对象的有限作用域以及临时对象(有限作用域实现)析构函数释放内存。当然,智能指针还不止这些,还包括复制时可以修改源对象等。智能指针根据需求不同,设计也不同(写时复制,赋值即释放对象拥有权限、引用计数等,控制权转移等)。auto_ptr
即是一种常见的智能指针。

比如上面类hasPtr中的成员ptr就可以跟引用计数绑定到一起,然后写到同一个类中:

<span style="font-family:Verdana;color:#333333;">/************************************************************************/
/*
*	定义一个单独的具体类用以封装使用计数和相关指针
*
*/
/************************************************************************/
//class U_Ptr , private class for use by hasPtr only
class U_Ptr
{
private:
//将HasPtr设置成为友元类,使其成员可以访问U_Ptr的成员
friend class HasPtr;

U_Ptr(int *p):  ip(p) , use(1) {}
~U_Ptr() { delete ip; }

private:
int *ip;
size_t use;
};</span>

将所有的成员都设置成为private:我们不希望普通用户使用U_Ptr类,所以他没有任何public成员!

HasPtr类需要一个析构函数来删除指针。但是,析构函数不能无条件的删除指针。”
      条件就是引用计数。如果该对象被两个指针所指,那么删除其中一个指针,并不会调用该指针的析构函数,因为此时还有另外一个指针指向该对象。看来,智能指针主要是预防不当的析构行为,防止出现悬垂指针。



     如上图所示,HasPtr就是智能指针,U_Ptr为计数器;里面有个变量use和指针ip,use记录了*ip对象被多少个HasPtr对象所指。假设现在又两个HasPtr对象p1、p2指向了U_Ptr,那么现在我delete  p1,use变量将自减1,  U_Ptr不会析构,那么U_Ptr指向的对象也不会析构,那么p2仍然指向了原来的对象,而不会变成一个悬空指针。当delete p2的时候,use变量将自减1,为0。此时,U_Ptr对象进行析构,那么U_Ptr指向的对象也进行析构,保证不会出现内存泄露。 

   新的HasPtr类保存一个指向U_Ptr对象的指针,U_Ptr对象指向实际的int基础对象:
<span style="font-family:Verdana;color:#333333;">class Hasptr
{
public:
Hasptr(int *p,int i):ptr(new U_Ptr(p)),val(i)
{
cout << "HasPtr constructor called ! " << "use = " << ptr->use << endl;
}
//赋值控制成员
Hasptr(const Hasptr &org):ptr(org.ptr),val(org.val)
{
++ptr->use;
cout << "HasPtr copy constructor called ! " << "use = " << ptr->use << endl;
}
Hasptr &operator=(const Hasptr &rhs){
// 增加右操作数中的使用计数
++rhs.ptr->use;
// 将左操作数对象的使用计数减1,若该对象的使用计数减至0,则删除该对象
if(-- ptr ->use==0)
delete ptr;
ptr=rhs.ptr ;
val=rhs.val ;
return *this;
}
~Hasptr()
{
cout << "HasPtr distructor called ! " << "use = " << ptr->use << endl;
if(--ptr->use==0)
delete ptr;
}
//return value
int *get_ptr() const
{
return ptr->ip;
}
int get_val() const
{
return val;
}

//non const members changes the indicated members
void set_ptr(int *p)
{
ptr->ip=p;
}
void set_val(int i)
{
val=i;
}

//
void set_ptr_val(int val)
{
*ptr->ip=val;
}
int get_ptr_val() const
{
return *ptr->ip;
}
private:
int val;
U_Ptr *ptr;
};</span>


    复制构造函数从形参复制成员并增加使用计数的值。复制构造函数执行完毕后,新创建对象与原有对象指向同一U_Ptr对象,该U_Ptr对象的使用计数加1。

    析构函数将检查U_Ptr基础对象的使用计数。如果使用计数为0,则这是最后一个指向该U_Ptr对象的HasPtr对象,在这种情况下,HasPtr析构函数删除其U_Ptr指针。删除该指针将引起对U_Ptr析构函数的调用,U_Ptr析构函数删除int基础对象。

    赋值操作符在减少左操作数的使用计数之前使rhs的使 用计数加1,从而防止自身赋值。如果左右操作数相同,赋值操作符的效果将是U_Ptr基础对象的使用计数加1之后立即减 1。
<span style="font-size:14px;">#include <iostream>
#include "Hasptr.h"
using namespace std;

int main ()
{
int *obj =new int(12);

Hasptr ptr1(obj,42);					//注意: obj一定是在堆上申请的内存,不能是在栈上
//copy 后 &ptr1.ptr==&ptr2.ptr
cout<<ptr1.get_ptr()<<'\t'<<ptr1.get_ptr_val()<<'\t'<<ptr1.get_val()<<endl;

Hasptr ptr2(ptr1);
cout<<ptr2.get_ptr()<<'\t'<<ptr2.get_ptr_val()<<'\t'<<ptr2.get_val()<<endl;

//因为ptr2是ptr1  copy过来的,而set_ptr_val()函数仅仅修改的是*ptr的值
//ptr 的地址仍然不变,ptr1和ptr2还是相同
ptr1.set_ptr_val(34);
cout<<ptr1.get_ptr()<<'\t'<<ptr1.get_ptr_val()<<'\t'<<ptr1.get_val()<<endl;
cout<<ptr2.get_ptr()<<'\t'<<ptr2.get_ptr_val()<<'\t'<<ptr2.get_val()<<endl;

//这里使用了智能指针后, 只是引用计数加1,指针还是同一个,所以值相同
int *new_obj = new int(44);			//注意: obj一定是在堆上申请的内存,不能是在栈上
ptr1.set_ptr(new_obj);
cout<<ptr1.get_ptr()<<'\t'<<ptr1.get_ptr_val()<<'\t'<<ptr1.get_val()<<endl;
cout<<ptr2.get_ptr()<<'\t'<<ptr2.get_ptr_val()<<'\t'<<ptr2.get_val()<<endl;

int *ip=new int (43);
Hasptr ptr(ip,10);

/*
* 我们不能直接调用 delete ip , 因为这样直接释放掉堆上的内容,导致智能指针ptr
* 指向的内容也不复存在
*/
//delete ip; //
ptr.set_ptr_val(0);
cout<<ptr.get_ptr()<<'\t'<<ptr.get_ptr_val()<<'\t'<<ptr.get_val()<<endl;

system("pause");
return 0;
}</span>
其结果如下:

从结果中我们可以看到:

1.先创建一个HasPtr 对象ptr1,这会调用构造函数,引用计数初始化为1. 然后调用复制构造函数,可以看到指针的地址是一样的,

只是use变为2.

2.当调用ptr1.set_ptr(new_obj);后 两个对象的指针值都相同(其实是同一个指针),说明它们共享这个指针

3.在return 0;语句之前可以看到,先后调用两个对象的析构函数,分别导致智能指针U_ptr的use为2, 1,然后调用U_ptr的析构函数释放掉内存。
3 定义值型类

[cpp] view
plaincopy





class HasPtr  

{  

private:  

    HasPtr(const int &p,int i):ptr(new int(p)),val(i) {}  

  

    //复制控制  

    HasPtr(const HasPtr &rhs):ptr(new int(*rhs.ptr)),val(rhs.val) {}
 //<复制构造函数中同样申请一个指针

    HasPtr &operator=(const HasPtr &rhs);  

    ~HasPtr()  

    {  

        delete ptr;  

    }  

  

   ........

public:  

    int *ptr;  

    int val;  

};  

  复制构造函数不再复制指针,它将分配一个新的int对象,并初始化该对象以保存与被复制对象相同的值。每个对象都保存属于自己的int值的不同副本。因为每个对象保存自己的副本,所以析构函数将无条件删除指针

    赋值操作符也因而不用分配新对象,它只是必须记得给其指针所指向的对象赋新值,而不是给指针本身赋值:

[cpp] view
plaincopy





HasPtr &HasPtr::operator=(const HasPtr &rhs)  

{  

    *ptr = *rhs.ptr;  //<初始化对象就可以了

    val = rhs.val;  

  

    return *this;  

}  

    即使要将一个对象赋值给它本身,赋值操作符也必须总是保证正确。本例中,即使左右操作数相同,操作本质上也是安全的,因此,不需要显式检查自身赋值。

继续学习文章:http://www.cnblogs.com/TenosDoIt/p/3456704.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  智能指针 c++