C++智能指针剖析

摘要

前些天,刚参加一场小的实习生面试,,,,发现面试官经常会问到的一个知识点 、----【智能指针】;;;
智能指针是C++知识中一个很常会被问到问题 ,,,,可惜的是当时没又出来。。。。
回来后,,,认真复习的了解了一下这块的知识。。。。

下面是我做的一些智能指针的自我见解。。。


主要内容有:
1、智能指针出现的背景  ;;;
2、智能指针的简单介绍;;;;
3、boost库对于 智能指针的优化实现;;;;
4、分析在不同的场合使用什么类型的智能指针。。。。

正文

1、智能指针的出现

我们先来看看下面的一段代码;;;

void remodel(std::string & ps)
{
	string * str = new string(ps);
	if (weird_thing())
		throw exception();
	ps = *str; 
	delete str;
	return;
}
在这段代码中 ,,要是(函数weird_thring())返回值为true的话,,,会直接抛出异常;;;到最后 、、、我们动态开辟的string对象str,,,,可能就没有被释放  、、、这就会造成内存的泄露 。。。。
很多人可能会想到要是在抛异常的前面,,,,delete掉这个对象的话 ,那么就不会的产生这个问题了。。。
但是 、、、很多的时候这种问题就是不可避免。。。。。
这时我们的编程人员就在想要是这个指针能够自己释放的话。那就什么都不是问题了 。。。


为了彻底解决这种问题 、、、、这时智能指针就此诞生了。。。。


2、智能指针的简单介绍

所谓智能指针,,,也就是为我们来管理动态开辟空间指针。。。。。它会在这个指针的作用域结束的时候 ,,,为我们释放掉这段空间 。。。。
当下出现的智能指针有      【auto_ptr】【unique_ptr】【scoped_ptr】【shared_ptr】【weak_ptr】
对于所有的智能指针 的构造函数  都应该是显式调用  (构造函数前面加上explicit) ,,,,
这样的话,,就不能直接将 指针 转换成智能指针对象 。。。

下面实现的是   auto_ptr   ;;;;;



//auto_ptr实现的智能指针 其中的  不涉及的深拷贝 ,,,,有缺陷 
template<class T>
class  auto_ptr
{
public:
	//定义成显式的构造函数,,, 用防止类型的隐式转换
	explicit  auto_ptr(T* ptr  = NULL)
		:_ptr(ptr)
	{}
	~auto_ptr()
	{
		if(_ptr)
		{
			delete _ptr;
		}
		_ptr = NULL;
	}
	T& operator*(){};
	T* operator->(){};
protected:
	T* _ptr;
};

auto_ptr的实现是在C++98提出来的,,,C++11已经将只摈弃了 ,,,但是这种    智能指针还是很有必要的。。。
但是对于   ,,,auto_ptr   没有涉及到   我们平常所说的深浅拷贝的问题 ,,,
因此、、、如果调用拷贝构造、还有赋值运算符的重载的话 ,,,,,,指针释放的后就会报错。。。


所以 、、、在这个的基础上  ,产生了 unique_ptr这个智能指针。。。

//相对于auto_ptr来说的话 ,,,,增加了  一个变量来表示指针的合法性 
//一个智能指针真正代表的只有一个 ,,,,那就是最新定义,,,或者赋值的那个 对象 
//但是还是存在的是    深浅拷贝的问题
template<class T>
class unique_ptr
{
public:
	explicit  unique_ptr(T* ptr =NULL)
		:_ptr(ptr)
		,_owner(true)
	{}
	unique_ptr(unique_ptr<T> & ap)
		:_ptr(ap._ptr)
		,_owner(true)//将最新拷贝定义的对象    设置为  true
	{
		if(ap._owner == true)
		{
			ap._owner = false;//旧的对象    设置为 false
		}
		else
		{
			_ptr = NULL;
			_owner = false;
		}
		
	}

	unique_ptr<T> & operator=(const  unique_ptr<T> & ap)
	{
		if(ap._owner == true&&_ptr != ap._ptr)
		{
			if(_ptr && _owner == true)
			{
				delete _ptr;
			}
			_ptr  = ap._ptr;
			_owner = true;//新的设置成  true
			ap._owner  =false;
		}
		return *this;
	}


	~unique_ptr()
	{
		if(_ptr && _owner == true)
		{
			delete _ptr;
		}
		_ptr  =NULL;
		_owner  =false;
	}

protected:
	T*  _ptr;
	bool _owner;//记录当前指针的合法
};



相对于   auto_ptr来说的话 ,,,,unique_ptr  有了 一定的优化 ,,
unique_ptr     会对最近拷贝建立的、或者赋值的对象    附上合法性  ,,,这样  内存释放的时候就不会报错了。
但是,,,,unique_ptr  则产生了新的问题 ,,,,,,就是要是当前的对象不合法的话  ,,,就不具有有效性 。。。

要是我们不调用 拷贝函数 、、、与赋值函数的话 ,,,那么就不会有上面的问题了 。。。。

所以、、、就有了   scoped_ptr  防拷贝的智能指针。。

//对于上面的智能指针产生的问题 ,,,都是由于的是 拷贝还有   赋值引发的深浅拷贝问题 
//导致的是 对象析构是的指针释放的问题 。。。。。
//所以就又产生了   一个新的指针,,,那就是 scoped_ptr
//用来     防止拷贝与 赋值


template<class T>
class scoped_ptr
{
public:
	explicit scoped_ptr(const  T* ptr = NULL)
		:_ptr(ptr)
	{}
	~scoped_ptr()
	{
		if(_ptr)
		{
			delete _ptr;
		}
		_ptr =NULL;
	}
protected:
	//将拷贝与赋值定义成私有的,,,就能防止拷贝
	scoped_ptr(const  scoped_ptr<T> &  ap);
	scoped_ptr<T>&  operator=(const  scoped_ptr<T> & ap);
protected:
	 T* _ptr;
};


上面的智能指针,,,看似合理、、、但是都有很大的缺陷。。。。。只能使用在一些特殊的场合。。。


所以就产生了  。。。。
所谓额 shared_ptr  、、、、使用引用计数实现的深拷贝 。。。。。

template<class T>
class shared_ptr
{
public:
	explicit shared_ptr(T* ptr =NULL)
		:_ptr(ptr)
		,_count(NULL)
		
	{
		if(_ptr)
			_count = new  int(1);
	}
	
	shared_ptr(const shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		,_count(sp._count)
	{
		if(_ptr)
		{
			(*_count)++;
		}
	}

	shared_ptr<T>& operator=(const shared_ptr<T>& sp )
	{
		if(_ptr!= sp._ptr)
		{
			if(*_count == 1)
			{
				delete _ptr;
				delete _count;
			}
			_ptr = sp._ptr;
			_count = sp._count;
			if(_ptr)
			{
				(*_count)++;	
			}	 
		}
		return *this;
	}
	~shared_ptr()
	{	
		if(_ptr)
		{
			if(*_count == 1)
			{
				delete _ptr;
				delete _count;
			}
			else
			{
				(*_count )--;
			}
		}
		
		_ptr =NULL;
		_count  =NULL;	
	}

protected:
	T* _ptr;
	int *  _count;
	
};


这类 的实现,,,只是初步的实现   释放空间时 的问题 。。。。
但是。。。。这只是简单的实现而已,,,
简化版的shared_ptr看上去不错,,,但是还存在很多问题 :
1、引用计数存在着   线程 安全的问题  ;;;;
2、另外 循环引用的问题 。
3、定制的删除器 。。。。



3、boost库对智能指针的优化实现

boost 库是C++中对智能指针库的解析 。。。
其中对于shared_ptr进行了很大的优化。。。。


1、循环引用问题。。
上面所说的shared_ptr指针所存在的问题 ,,,,基本上的到了解决。。。。



在boost库中 ,,,使用一个弱引用智能指针(weak_ptr)来打破循环引用(weak_ptr不增加引用计数)

weak_ptr是一种 不控制的所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象  。将一个weak_ptr

绑定到一个shared_ptr上,不会改变shared_ptr的引用计数。一旦 最后一个指向对象的shared_ptr被销毁的话 ,对象就会被释放,即使weak_ptr有指向的对象,,对象也还是会被释放 。

因此,weak_ptr就是抓住了这种智能指针 "弱" 共享对象的特点。。。




2、定制删除器


我们都知道 ,,,有些指针在 它的作用域结束的话 ,就要进行 对它进行 合适的处理。。

但是这样的指针,不仅仅只有动态开辟的空间指针。。。例如:

就像是 文件指针,,我们在文件操作结束之后,,需要关闭文件 ,,,调用 fclose()函数 。。。

对于这样的指针,,我们实现的指针指针没有办法 ,,对它来进行     尾处理 

所以,就产生了有了这个定制的删除器问题 。。。


在boost库中为我们设计了这样的函数,,,我们要做的只是  设计一个定制的删除器的仿函数 。。。


#include <boost/shared_ptr.hpp>
using namespace boost;
// 定置的删除器仿函数
class FClose
{
public :
	void operator () (void* ptr)
	{
	cout<<"fclose" <<endl;
	fclose((FILE *)ptr);
	}
};
class Free
{
public :
	void operator () (void* ptr)
	{
	cout<<"free" <<endl;
	free(ptr );
	}
};
void Test ()
{
	// 定制删除器
	shared_ptr<FILE > p1( fopen("test.txt" , "w"), FClose());
	// 定制删除器和分配器
	shared_ptr<int > p2((int *)malloc( sizeof(int )), Free(), allocator<int >
	());
}



4、分析在不同的场合使用什么类型的智能指针

(该部分转载于他处)

在掌握了这几种智能指针后,大家可能会想另一个问题:在实际应用中,应使用哪种智能指针呢?
下面给出几个使用指南。

(1)如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。这样的情况包括:

  • 有一个指针数组,并使用一些辅助指针来标示特定的元素,如最大的元素和最小的元素;
  • 两个对象包含都指向第三个对象的指针;
  • STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出warning)和auto_ptr(行为不确定)。如果你的编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr。

(2)如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。如果函数使用new分配内存,并返还指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。可将unique_ptr存储到STL容器在那个,只要不调用将一个unique_ptr复制或赋给另一个算法(如sort())。例如,可在程序中使用类似于下面的代码段

unique_ptr<int> make_int(int n)
{
    return unique_ptr<int>(new int(n));
}
void show(unique_ptr<int> &p1)
{
    cout << *a << ' ';
}
int main()
{
    ...
    vector<unique_ptr<int> > vp(size);
    for(int i = 0; i < vp.size(); i++)
        vp[i] = make_int(rand() % 1000);              // copy temporary unique_ptr
    vp.push_back(make_int(rand() % 1000));     // ok because arg is temporary
    for_each(vp.begin(), vp.end(), show);           // use for_each()
    ...
}

其中push_back调用没有问题,因为它返回一个临时unique_ptr,该unique_ptr被赋给vp中的一个unique_ptr。另外,如果按值而不是按引用给show()传递对象,for_each()将非法,因为这将导致使用一个来自vp的非临时unique_ptr初始化pi,而这是不允许的。前面说过,编译器将发现错误使用unique_ptr的企图。

在unique_ptr为右值时,可将其赋给shared_ptr,这与将一个unique_ptr赋给一个需要满足的条件相同。与前面一样,在下面的代码中,make_int()的返回类型为unique_ptr<int>:

unique_ptr<int> pup(make_int(rand() % 1000));   // ok
shared_ptr<int> spp(pup);                       // not allowed, pup as lvalue
shared_ptr<int> spr(make_int(rand() % 1000));   // ok


模板shared_ptr包含一个显式构造函数,可用于将右值unique_ptr转换为shared_ptr。shared_ptr将接管原来归unique_ptr所有的对象。

在满足unique_ptr要求的条件时,也可使用auto_ptr,但unique_ptr是更好的选择。如果你的编译器没有unique_ptr,可考虑使用Boost库提供的scoped_ptr,它与unique_ptr类似。




  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在引用中,给出了一个程序的代码片段,这段代码涉及到了内存方面的问题。在这段代码中,函数`div()`用于进行两个整数的除法运算,如果除数为0,则会抛出`invalid_argument`异常。在`Func()`函数中,首先通过`new`关键字分别创建了两个`int`类型的指针变量`p1`和`p2`,然后调用了`div()`函数进行除法运算,并打印结果。最后,通过`delete`关键字释放了指针变量所指向的内存空间。 现在我们来分析一下这段程序中可能存在的内存方面的问题: 1. 如果在创建`p1`的语句后发生异常,那么`p1`指向的内存空间将无法被正确释放,造成内存泄漏。 2. 如果在创建`p2`的语句后发生异常,同样会导致`p2`指向的内存空间无法被正确释放,造成内存泄漏。 3. 如果在调用`div()`函数时发生异常,那么由于`p1`和`p2`指向的内存空间没有被正确释放,同样会造成内存泄漏。 为了解决这些内存问题,可以使用智能指针来管理动态分配的内存。智能指针可以自动管理内存的释放,避免内存泄漏的问题。在C++中,可以使用`std::shared_ptr`来管理动态分配的内存。它会在没有引用指向这块内存时自动释放它。 现在我们来回答相关问题: 相关问题: 1. 如果在创建`p1`的语句后发生异常,该如何处理? 在创建`p1`的语句后,可以将其包装在一个`std::shared_ptr`中,这样即使发生异常,智能指针也会自动释放其所指向的内存空间。 2. 如果在创建`p2`的语句后发生异常,该如何处理? 同样地,可以将`p2`包装在一个`std::shared_ptr`中,这样即使发生异常,智能指针也会自动释放其所指向的内存空间。 3. 如果在调用`div()`函数时发生异常,该如何处理? 可以使用`std::shared_ptr`来管理`p1`和`p2`,这样即使在调用`div()`函数时发生异常,智能指针也会自动释放`p1`和`p2`所指向的内存空间。 4. 如果多个线程同时进行拷贝操作,会出现什么问题? 在引用中,如果多个线程同时进行拷贝操作,可能会出现数据不一致的问题。这是因为在多线程环境下,共享指针的引用计数可能会出现竞争条件,导致引用计数不正确,从而无法正确地管理内存。 5. 如何使用智能指针来管理使用`new[]`创建的对象? 在引用中,如果使用`new[]`创建了对象,可以使用`std::shared_ptr`来管理这些对象。需要注意的是,`std::shared_ptr`默认使用`delete`来释放内存,而不是`delete[]`。所以,需要自定义删除器来使用`delete[]`来释放数组对象。 以上是关于C++智能指针的面试题的回答。如果您还有其他
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值