C++智能指针auto_ptr源码完全解析---以微软auto_ptr为例来探讨auto_ptr的用法

972 篇文章 329 订阅
73 篇文章 32 订阅

       对于C/C++程序员来说, 内存泄露是一个谈之色变的话题, 很多时候, 机器运行1天2天都是ok的, 但运行到一个星期后, 就卡得要死。 实际上, 很多时候是内存泄露造成的。 内存泄露很容易引入, 但是定位起来非常非常难, 在内存泄露初期, 通常没有异常症状, 但随着内存泄露的累积, 内存逐渐被啃光, 最终导致卡死或者死机。

 

       申请堆内存, 又没有正确释放, 就会导致内存泄露, 听起来好可怕啊, 那有没有什么办法可以解决这一难题呢? 有的! 人类的智慧还是很厉害的。 我么知道, 栈对象在离开其作用域的时候, 会自动调用析构函数, 所以, 可以考虑把某一栈对象与某一堆内存绑定,且在其析构函数中释放堆内存,  那么, 在该栈对象离开作用域时, 堆内存自动释放, 这就是智能指针(本质是栈对象)的原理,简直是妙招啊。  这个栈对象装得像指针一样, 所以我们称之为智能指针, 其实, 它不过就是个普通的栈对象而已。 

 

        在本文中, 我们来介绍智能指针中的一种------auto_ptr,  并从源码的角度来看看auto_ptr使用中存在的一些常见问题:

 

       我们先来看一段简单的程序:

 

#include <iostream>
#include <memory> // 有auto_ptr这个类模板
using namespace std;

int main()
{
	auto_ptr<int> p(new int(100));

	// p是个对象, 但重载了*运算符, 所以我说它很能装, 装得像个指针
	cout << *p << endl; // 100

	return 0;
}

 

 

      C++是一门复杂的语言, 比这个更糟糕的是: 一些不合格的C++程序员正在用它。 我们看看上面的程序, 实际上, 编译器做了太多的手脚, 这也是C++复杂的一个原因。 我们展开memory文件, 看看其中关于auto_ptr的代码, 复制过来, 然后形成如下程序:

 

#include <iostream>
using namespace std;


// 微软能把代码写成这样, 也是够风骚的

		// TEMPLATE CLASS auto_ptr
template<class _Ty>
	class auto_ptr {
public:
	typedef _Ty element_type;
	explicit auto_ptr(_Ty *_P = 0) _THROW0()
		: _Owns(_P != 0), _Ptr(_P) {}
	auto_ptr(const auto_ptr<_Ty>& _Y) _THROW0()
		: _Owns(_Y._Owns), _Ptr(_Y.release()) {}
	auto_ptr<_Ty>& operator=(const auto_ptr<_Ty>& _Y) _THROW0()
		{if (this != &_Y)
			{if (_Ptr != _Y.get())
				{if (_Owns)
					delete _Ptr;
				_Owns = _Y._Owns; }
			else if (_Y._Owns)
				_Owns = true;
			_Ptr = _Y.release(); }
		return (*this); }
	~auto_ptr()
		{if (_Owns)
			delete _Ptr; }
	_Ty& operator*() const _THROW0()
		{return (*get()); }
	_Ty *operator->() const _THROW0()
		{return (get()); }
	_Ty *get() const _THROW0()
		{return (_Ptr); }
	_Ty *release() const _THROW0()
		{((auto_ptr<_Ty> *)this)->_Owns = false;
		return (_Ptr); }
private:
	bool _Owns;
	_Ty *_Ptr;
	};


int main()
{
	auto_ptr<int> p(new int(100));

	// p是个对象, 但重载了*运算符, 所以我说它很能装, 装得像个指针
	cout << *p << endl; // 100

	return 0;
}

       看了上面auto_ptr的源码, 正在喝水的我, 差点呛着了。 并想问: 微软, 你还能再风骚一点么疑问

 

 

       好吧, 我也懒得计较了。 于是, 对上面代码进行风格整理, 并作出详细的注释, 就算是剖析一下auto_ptr的源码吧:

 

#include <iostream>
using namespace std;


// 简单类
class A
{
public:
	void fun()
	{
	
	}
};


template<class T>

// 类模板
class auto_ptr 
{
public:

	// explicit构造函数, 禁止类型转化
	explicit auto_ptr(T *p = 0) throw()
		: m_bIsOwner(p != 0), m_ptr(p) 
	{
		cout << "debug1" << endl;
	}

	// owner转移
	auto_ptr(const auto_ptr<T>& y) throw()
		: m_bIsOwner(y.m_bIsOwner), m_ptr(y.release()) 
	{
		cout << "debug2" << endl;
	}

	// owner转移
	auto_ptr<T>& operator=(const auto_ptr<T>& y) throw()
	{
		cout << "debug3" << endl;

		if (this != &y) // 当前对象不是y对象
		{
			cout << "debug4" << endl;

			if (m_ptr != y.get()) // 当前对象绑定的地址不是y对象绑定的地址
			{
				cout << "debug5" << endl;

				if (m_bIsOwner) // 如果当前对象已经绑定堆, 则要先释放
				{
					cout << "debug6" << endl;
					delete m_ptr;
				}

				cout << "debug7" << endl;

				m_bIsOwner = y.m_bIsOwner; // 转移owner
			}
			else if (y.m_bIsOwner) // 当前对象与y绑定到同一块堆上, 且y是owner, 则把y的owner转移给当前对象
			{
				cout << "debug8" << endl;

				m_bIsOwner = true;
			}

			cout << "debug9" << endl;

			m_ptr = y.release();  // 让y不再是owner
		}

		cout << "debug10" << endl;

		return *this;  // 返回当前对象的引用
	}

	// 析构函数
	~auto_ptr()
	{
		cout << "debug11" << endl;	

		if (m_bIsOwner) // 只有拥有owner属性才释放堆, 这样避免重复释放
		{
			cout << "debug12" << endl;

			delete m_ptr;  // 即使m_ptr是空指针也木有关系
		}
	}

	// 重载对象的*运算符, 使得对象"看起来"像指针, 可以执行*p操作
	T& operator*() const throw()
	{
		cout << "debug13" << endl;

		return *get(); 
	}

	// 重载对象的->运算符
	T *operator->() const throw()
	{
		cout << "debug14" << endl;

		return get(); 
	}

	// 获得对象绑定的地址
	T *get() const throw()
	{
		cout << "debug15" << endl;

		return m_ptr; 
	}

	// 去掉对象的owner属性
	T *release() const throw()
	{
		cout << "debug16" << endl;

		((auto_ptr<T> *)this)->m_bIsOwner = false;
		return m_ptr; 
	}
	
private:
	bool m_bIsOwner;  // 对象是否拥有为owner的标志
	T *m_ptr; // 对象绑定的指针
};


int main()
{
	{
		cout << "------------------------------" << endl;

		// 用法错误, 因为构造函数中有explicit, 不允许类型转化
		//auto_ptr<int> p = new int(10);
	}


	{
		cout << "------------------------------" << endl;

		// ok
		auto_ptr<int> p(new int(10));
	}


	{	
		cout << "------------------------------" << endl;

		// 下面代码有严重的运行期错误, 实际上是尝试delete栈上的内容
		int a = 10;
		//auto_ptr<int> p(&a);
	}


	{	
		cout << "------------------------------" << endl;

		auto_ptr<int> p(new int(10));

		// 错误, p虽然"看似像"指针, 其本质是对象, delete p;是未定义行为
		//delete p;
	}


	{	
		cout << "------------------------------" << endl;

		int *q = new int(10);
		auto_ptr<int> p(q);

		// 错误, q释放一次, p释放一次, 重复释放啊
		//delete q;
	}


	{	
		cout << "------------------------------" << endl;

		auto_ptr<int> p0;

		// 有debug3的打印, 但没有debug4, 知道原因了吧
		p0 = p0;
	}


	{		
		cout << "------------------------------" << endl;

		auto_ptr<int> p0(new int(10));

		// 注意, 这是初始化, 不是复制, 所以不会有debug3的打印
		auto_ptr<int> p1 = p0; 
	}


	{		
		cout << "------------------------------" << endl;

		auto_ptr<int> p0(new int(10));
		auto_ptr<int> p1; 

		// 注意, 这才是赋值, 所有有debug3, debug4, debug5, debug7, debug9, debug10的打印
		// 为什么没有debug6呢? 因为当前对象p1还不是owner
		p1 = p0;
	}


	{		
		cout << "------------------------------" << endl;

		auto_ptr<int> p0(new int(10));
		auto_ptr<int> p1(new int(20));
		
		// 有debug6的打印, 因为当先释放p1绑定的对象, 否则内存又泄露了啊
		p1 = p0;
	}


	{		
		cout << "------------------------------" << endl;

		auto_ptr<int> p0(new int(10));

		// 把owner转给p1
		auto_ptr<int> p1(p0);

		// 终于见到你了, debug8
		p0 = p1;
	}

	{		
		cout << "------------------------------" << endl;

		auto_ptr<int> p(new int(10));

		// 见到你了, debug13
		cout << *p << endl;
	}

	
	{		
		cout << "------------------------------" << endl;

		auto_ptr<A> p(new A());

		// 终于见到你了, debug15
		p->fun();
	}


	{		
		cout << "------------------------------" << endl;

		auto_ptr<int> p0(new int(10));
		auto_ptr<int> p1(p0);
		auto_ptr<int> p2(p1);

		// 实际上, p3才是最后的winner, 才是最后的owner, 所以释放堆的重任在p3身上
		auto_ptr<int> p3(p2);
	}


	{		
		cout << "------------------------------" << endl;

		// oh, my god, 内存泄露, 本来要delete [] q; 现在析构函数只执行delete q;
		int *q = new int[3];
		auto_ptr<int> p(q);
	}


	{		
		cout << "------------------------------" << endl;

		// oh, my god, 内存泄露, 本来要delete [] q; 现在析构函数只执行delete q;
		int *q = new int[3];
		auto_ptr<int> p(q);

		// 已经说过, 下面语句会造成内存重复释放
		//delete q;
	}


	// 最后说明一下,  auto_ptr不适合做容器的元素, 这一点我们以后会再次讨论到


	return 0;
}

    
       好了, 不多说auto_ptr了, 一切尽在代码中。

 



 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值