1,问题的由来
当类里包含一个指针成员,而该成员又指向一个动态分配的对象时,该对象的复制行为可能会导致错误。因为如果我们只复制指针的值,而不重新开辟空间,则多个对象里的指针指向同一块内存。当某一个对象调用析构函数收回了这块内存,则所有的对象的指针成员都成为悬垂指针。2,智能指针(smart pointer)概念
智能指针是一个行为类似指针,但也提供其他功能的类。智能指针的一个通用形式接受指向动态分配对象的指针,并负责删除该对象。用户分配对象,但由智能指针类删除它。智能指针类需要实现复制控制成员来管理指向共享对象的指针。只有在撤消了指向共享对象的最后一个智能指针后,才删除共享对象。使用计数(use count)是实现智能指针的最常用方式。3,使用计数(use count)
复制控制成员中使用的编程技术。使用计数与共享对象一起存储。需要创建一个单独类指向共享对象并管理使用计数。由构造函数,而不是复制构造函数,设置共享对象的状态,并将使用计数置为1.每当由复制构造函数和赋值操作符生成一个新副本时,使用计数加1.由析构函数撤销对象或作为赋值操作符的左操作数撤销对象时,使用计数减1.赋值操作符和析构函数检查使用计数是否已减至0,如果是,则撤销对象。
注意:这里故意不定义默认构造函数,因为每个对象都必须绑定。
4,例1
/*******************************************************************/
// 智能指针
/*******************************************************************/
class HasPtr;
class Smart_Ptr
{
private:
friend HasPtr;
int *ip; //指向目标对象
size_t iNr; //统计个数
Smart_Ptr(int *ptr) :ip(ptr), iNr(1) { cout << "调用Smart_Ptr构造函数" << endl; }
~Smart_Ptr()
{
cout << "调用Smart_Ptr析构函数" << endl;
cout << "ip = " << ip << " " << "*ip = " << *ip << endl;
delete ip;
}
};
class HasPtr
{
public:
int get_val() { return val; }
int* get_ptr() { return sPtr->ip; }
int get_ptr_val() { return *sPtr->ip; }
size_t get_Nr() { return sPtr->iNr; }
<span style="white-space:pre"> </span>// <span style="color:#ff0000;">无默认构造函数</span>
//下面注释掉的构造函数定义方法不对,因为定义出的obj作用域只在构造函数内,构造函数结束后obj就析构,释放p
//HasPtr(int *p, int i) :val(i) { Smart_Ptr obj(p);sPtr = &obj;}
HasPtr(int *p, int i) :sPtr(new Smart_Ptr(p)), val(i) { cout << "调用HasPtr构造函数" << endl; }
HasPtr(const HasPtr& one) :sPtr(one.sPtr), val(one.val) //复制构造函数
{
++sPtr->iNr;
cout << "调用HasPtr复制构造函数" << endl;
}
HasPtr& operator=(const HasPtr &one) //赋值操作符
{
cout << "开始赋值操作" << endl;
++one.sPtr->iNr;//请注意一定是先加后减,否则当左右操作数是同一个对象时就出错了。
if (--sPtr->iNr == 0)
{
cout << "赋值操作导致删除智能指针" << endl;
delete sPtr;
}
sPtr = one.sPtr;
val = one.val;
cout << "调用HasPtr赋值操作符" << endl;
return *this;
}
~HasPtr()//析构函数
{
cout << "是否删除sPtr: sPtr->iNr = " << sPtr->iNr << endl;
if (0 == --sPtr->iNr)
{
cout << "析构函数导致删除智能指针" << endl;
delete sPtr;
}
cout << "调用HasPtr析构函数" << endl;
}
private:
Smart_Ptr *sPtr;
int val;
};
int main()
{
int *secret = new int(3132);
cout << "*secret = " << *secret << endl;
HasPtr(secret, 520);//临时对象
if (NULL == secret)
{
cout << "secret has gone!" << endl;
}
else
{
cout << "keep the secret!" << endl;
cout << "*secret = " << *secret << endl;
}
}
运行结果:
有以下几点需要注意:
1,HasPtr(secret, 520);创建了一个没有名字的临时对象,构造后立马析构;
2,用delete删除指针后,并不会将指针置为空,但是指针里的内容已经不可取了。
3,delete一个对象时会调用该对象的析构函数,而operator delete(obj)删除对象时直接收回,不会调用析构函数;
4,注意构造函数中sPtr(new Smart_Ptr(p))的用法,将初始化式和new运算符结合在一起使用
5,只能指针对象应当在HasPtr的构造函数中动态创建,而不能成为该构造函数的局部变量,否则构造函数一结束,智能指针对象就析构了;
5,例2
类定义部分不变,同例1void show_smart_point(HasPtr &orig)
{
int *new_secret = new int(1314);
HasPtr copy_1(orig);
cout <<"after copy_1:"<< "orig.iNr = " << orig.get_Nr()<<"\n" << endl;
HasPtr orig_2(new_secret, 1);
//下面注释掉的写法会会导致函数退出时出错!orig_2析构时会释放new_secret,导致orig_3无法析构
//HasPtr orig_3(new_secret, 2);
HasPtr orig_3(new int(3132), 2);
cout << "after orig_2:" << "orig.iNr = " << orig.get_Nr() << "\n" << endl;
orig_2 = orig;
cout << "after = :" << "orig.iNr = " << orig.get_Nr() << "\n" << endl;
cout << "orig.iNr = " << orig.get_Nr() << endl;
}
int main()
{
HasPtr orig(secret, 520);
cout << orig.get_ptr_val() << endl;
cout << "\nbefore call show_smart_point" << endl;
show_smart_point(orig);
cout << "\nafter call show_smart_point" << endl;
cout << "orig.iNr = " << orig.get_Nr() << endl;
system("pause");
return 0;
}
运行结果:
请注意:
1,虽然说只能指针能防止垂悬指针,但是, 如果创建两个新对象orig_2和orig_3,且这两个对象同时指向一块内存new_secret,则当orig_2由于赋值操作导致原先指向的内存被收回时,会让orig_3中的指针成为垂悬指针。,
2,智能指针的功能不能用static数据成员来简单实现。因为因为就像orig_1和orig_2是同一种类型,如果有static成员的话,他们的值相同,但是,它们指向不同的动态分配对象,有不同的副本个数,不能用一个static成员来描述。