C++学习笔记38——智能指针

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

类定义部分不变,同例1
void 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成员来描述。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值