前景摘要
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,否则会两次释放同一块内存,会出错的!