C++智能指针初探

344 阅读4分钟

裸指针

在学习c的时候常常被指针的强大锁折服,因为只要有数据的地址,那就可以玩出花来。并且由于c语言的面向过程的设计思想,很少能出问题,但是在学习c++的时候就有各种的问题出现,比如:

  • 忘记释放资源,导致资源泄露(常发生内存泄漏问题)
  • 同一资源释放多次,导致释放野指针,程序崩溃
  • 明明代码的后面写了释放资源的代码,但是由于程序逻辑满足条件,从中间return掉了,导致释放资源的代码未被执行到,懵
  • 代码运行过程中发生异常,随着异常栈展开,导致释放资源的代码未被执行到,懵

智能指针

c++具有兼容c的能力,因此也是继承了c的指针,在开发过程中如果使用裸指针,那必须要为他负责,这是一份巨大的责任!但为了觊觎裸指针巨大的功能,又不得不使用它,因此为了拥有裸指针的强大功能,还不影响c++的工程能力,就诞生了智能指针。

智能指针重点在智能:可以自动释放申请的资源,而不用用户关注资源的释放。

实现原理

反观程序代码运行过程,其实可以发现,当栈上的对象出作用域会自动释放(析构),因此,如果要实现上述所说的自动释放申请的资源,那么我们只需要将资源释放的代码全部放在这个析构函数中执行,就达到了所谓的智能指针,对比代码:

  1. 使用裸指针
 int main()
{
	int *p = new int;
	/*
	如果这里忘记写delete,或者上面的代码段中程序return掉了,
	没有执行到这里,都会导致这里没有释放内存,内存泄漏
	*/
	delete p;

	return 0;
}
  1. 使用智能指针
template<typename T>
class CSmartPtr
{
public:
	CSmartPtr(T *ptr = nullptr) :mptr(ptr) {}
	~CSmartPtr() { delete mptr; }
private:
	T *mptr;
};

int main()
{
	CSmartPtr<int> ptr(new int);
	...
	/*由于ptr是栈上的智能指针对象,不管是函数正常执行完,还是运行过程中出现
	异常,栈上的对象都会自动调用析构函数,在析构函数中进行了delete
	操作,保证释放资源*/
	return 0;
}

从上面两段代码中可以隐约揣摩出智能指针的大概实现思路了,这里总结两点:

  1. 智能指针体现在把裸指针进行了一次面向对象的封装,在构造函数中初始化资源地址,在析构函数中负责释放资源
  2. 利用栈上的对象出作用域自动析构这个特点,在智能指针的析构函数中保证释放资源

上文中所说的,我们既要裸指针的强大还要弥补它的缺点。到现在为止,其实大概了解了智能指针是如何释放资源的,这就是智能指针如果弥补裸指针的缺点的,接下来看看智能指针是如何使用裸指针的强大:

template<typename T>
class CSmartPtr
{
public:
	CSmartPtr(T *ptr = nullptr) :mptr(ptr) {}
	~CSmartPtr() { delete mptr; }

	T& operator*() { return *mptr; }
	const T& operator*()const { return *mptr; }

	T* operator->() { return mptr; }
	const T* operator->()const { return mptr; }
private:
	T *mptr;
};
int main()
{
	CSmartPtr<int> ptr(new int);
	*ptr = 20;
	cout << *ptr << endl;
	return 0;
}

买了个关子,其实要继承裸指针的强大,就是重载了*和->两种运算符。这下就基本接近完美了。但是,运行下面代码你会发现程序crash:

int main() { 
    CSmartPtr<int> ptr1(new int);
    CSmartPtr<int> ptr2(ptr1); 
    return 0; 
}

这个main函数运行,代码直接崩溃,问题出在默认的拷贝构造函数做的是浅拷贝,两个智能指针都持有一个new int资源,ptr2先析构释放了资源,到ptr1析构的时候,就成了delete野指针了,造成程序崩溃。所以这里引出来智能指针需要解决的两件事情:

  1. 怎么解决智能指针的浅拷贝问题
  2. 多个智能指针指向同一个资源的时候,怎么保证资源只释放一次,而不是每个智能指针都释放一次,造成代码运行不可预期的严重后果

要解决上述问题,我们就要去c++提供的四种智能指针中找答案了。 c++ 四种智能指针学习