1)在复制控制里面,一般复制成员都是复制其值,复制指针只复制指针的地址,而不会复制指针指向的对象。
2) 将一个指针复制到另一个指针时,两个指针指向同一对象,删除其中任意一个,都对另一个有硬性,特别是动态分配的内存,会导致程序崩溃。
3) 如何使用指针呢?这里就牵涉几种情况:
a)指针成员采取常规指针行为 :指针的缺陷但无需特殊的复制控制
b)类采取智能指针行为:指针指向的对象是共享的,但能防止悬垂指针
c)类采取值型行为:指针指向的对象时唯一的,由每个类对象单独管理
如下一个普通的指针类,包含两个成员,一个int型指针,一个int型的普通变量:
class HasPtr { public: HasPtr(void); ~HasPtr(void); HasPtr(int *p, int i):ptr(p), val(i) { }; //HasPtr(const HasPtr&); //HasPtr& operator=(const HasPtr&); int* GetPtr() const { return ptr; } int GetVal() const { return val; } int SetPtr(int *p) { ptr = p; return 0; } int SetVal(int i) { val = i; return 0; } int GetPtrVal() const { return *ptr; } int SetPtrVal(int val) { *ptr = val; return 0;} private: int *ptr; int val; };如下调用:
int nObj = 100; HasPtr p1, p2; p1 = HasPtr(&nObj, 10); // 复制构造函数和复制操作符都被调用 p2 = HasPtr(p1); HasPtr p3 = HasPtr(&nObj, 10); //只发生了复制构造函数的调用 HasPtr p4 = HasPtr(p3); p1.SetVal(2); cout << p1.GetVal() << " " << p2.GetVal() << endl; p1.SetPtrVal(18); cout << p1.GetPtrVal() << " " << p2.GetPtrVal() << endl;其中val的值相对于类对象时独立的,而指针却是共享的,就会出现下面的悬垂指针,如下
int *ip = new int(189); HasPtr ptr(ip, 123); delete ip; ip = NULL; ptr.SetPtrVal(124); //此时指针对象已经被释放最后一个在设置之前,ptr就应经被释放了,这时候再赋值就找不到内存空间了
如果管理指针,如果直接控制有问题,那我们就间接控制,将指针绑定与一个单独的类,而使用类对象指针进行操作,主要增加使用计数count,来判定多少个对象还在使用资源,智能指针的设计如下
class U_Ptr { friend class HasPtr; U_Ptr(int *p = NULL): ip(p), count(1) { }; //U_Ptr(const U_Ptr& uptr); //U_Ptr& operator=(const U_Ptr& uptr); ~U_Ptr() { }//delete ip; 释放一个不存在的指针 private: int *ip; size_t count; };那这里也需要重新设计包含指针的类了,必须先更改其构造函数,让指针从属于类,由类自己自由操纵,所以设为动态分配;复制构造函数和赋值操作符函数也要重新设计,复制构造函数分配新元素并从被复制对象处复制值,赋值操作符撤销所保存的原对象并从右操作数向左操作数复制值;而析构函数就需要释放构造函数分配的资源。如下:
class HasPtr { public: HasPtr(void); ~HasPtr(void); HasPtr(int *p, int i); HasPtr(const HasPtr&); HasPtr& operator=(const HasPtr&); int* GetPtr() const { return ptr->ip; } int GetVal() const { return val; } int SetPtr(int *p) { ptr->ip = p; return 0; } int SetVal(int i) { val = i; return 0; } int GetPtrVal() const { return *ptr->ip; } int SetPtrVal(int val) { *ptr->ip = val; return 0;} private: U_Ptr *ptr; int val; };
其他函数的设计不予说明,如下
HasPtr::HasPtr(int *p, int i) :ptr(new U_Ptr(p)), val(i) { cout << "two parameter constructor" << endl; }动态分配了一个新的堆内存,并将指针赋予ptr,这样ptr就间接指向了ip,而类对象就利用这个指针类类操纵ip了
HasPtr::HasPtr(const HasPtr &obj) :ptr(obj.ptr), val(obj.val) { ++obj.ptr->count; cout << "copy constructor" << endl; }这里主要增加使用计数,因为复制后就多了一个对象占有这个指针了
HasPtr& HasPtr::operator=(const HasPtr &obj) { cout << "operator = " << endl; #if 0 //注意这里的u_ptr可能指向两个不同的指针对象 ++obj.ptr->count; if (--ptr->count == 0) { delete ptr; } #else --ptr->count; ++obj.ptr->count; if (ptr->count == 0) { delete ptr; } #endif ptr = obj.ptr; val = obj.val; return *this; }这里赋值默认要先去掉之前对象的指针使用计数,然后再增加形参对象的计数,这里要判断一下,是否当前对象对指针的使用计数为0,如果为0就要释放那个指针,否则就会导致一块内存永远得不到释放。两种方式是一致的,主要考虑赋值自己的问题,理解方式不一样,不再深究。
HasPtr::~HasPtr(void) { if (--ptr->count == 0) { delete ptr; } cout << "destructor" << endl; }这里当指针计数为0的时候,肯定要释放他,为什么要减1,主要是由于对象的生命期已经结束了,没有结束就不会进入析构函数,所以指针计数肯定要-1。但是在C++primer里面对智能指针类里面的析构函数,再次释放是不正确的,看上面智能指针类里面的标注,HasPtr里面申请的资源必须由类自己来释放,而智能指针类u_ptr里面传入的p指向的对象其实就是HasPtr构造函数传入的形参p指向的对象,他是不是动态分配的,我们不知道,而且这个由类的使用者决定,所以智能指针不能使用delete ip,否则会两次释放同一块内存,会出错的!
原文:http://blog.csdn.net/comwise/article/details/19565555