C++异常&&智能指针

目录

异常

异常的定义

异常的抛出和捕获

异常安全问题

异常的规范

智能指针

RAII思想

使用RAII的例子

智能指针

文件资源

在linux中管理锁资源

智能指针发展历程

auto_ptr

unique_ptr

shared_ptr


异常

异常的定义

异常是一种处理运行时错误的机制,它允许程序在发生错误时能够以一种可预测和可控的方式响应,而不是使程序直接崩溃。异常是程序运行时抛出的一个信号,表明程序中出现了某种特殊的情况,需要被处理。

异常的抛出和捕获

异常的抛出:抛异常是对程序发生错误的预测,当程序猿自己都不知道自己的代码错误的情况时,异常也就无从抛出了。所以异常抛出的位置是由程序猿主观设定的,使用异常处理来确保程序的健壮性和可维护性。

语法:将可能抛出异常的代码块用try包裹,在try块内部throw 异常对象

异常的捕获:将抛出的异常在距离最近的且类型最匹配的catch块内进行捕获,对程序中出现某种异常情况进行处理。如果当前栈并没有捕获逻辑,则销毁当前栈继续向上寻找可以匹配的catch块来捕获,若到main函数栈依旧没有匹配的,则程序崩溃

语法:使用类似形参的方式接受异常对象,将处理异常的方式定义在catch块内部。

通常情况,可以将异常类设计为父子关系,具体的异常类都是继承自一个基类exception类,我们可以使用exception类形参来接受抛出的任何的异常对象,调用成员函数what()可以得知某个异常的具体描述,这也是多态的例子。

异常安全问题

异常安全问题是由于异常在抛出时,会停止执行当前try块的代码,寻找最近的最匹配的catch块进行捕获,这就使得某些释放资源的代码可能不会被执行,造成资源的泄露

举个栗子:

总结:这样的情况下必会造成资源泄露。

try{
  资源申请;
  异常的抛出;
  资源释放;
}
catch(exception& e)
{
  捕获逻辑;
}

资源泄露的种类:new delete内存泄露、open close文件描述符泄露、lock unlock死锁... 

C++中如何解决异常安全问题:

使用RAII思想,让对象管理资源,栈帧销毁时自动调用析构释放申请的资源。

异常的规范

异常的使用规范是为了让编程者知道哪些函数是否会抛出异常,会抛出什么类型的异常。当然也可以不遵守这个规范,但是写一份易维护的代码是编写者需要慢慢养成的素养。

void func() throw(异常类型A,异常类型B,异常类型C); //表明此函数可能抛出A,B,C三种类型的异常

void pow() throw(异常类型A); //表明此函数只可能抛出A这一种类型的异常

void run() throw(); //表明此函数不会抛出异常

智能指针

RAII思想

RAII(Resource Acquisition Is Initialization资源获取即初始化),是一种将资源的生命周期和对象的生命周期绑定在一起的思想,从而消除资源泄露的问题。

即可以让栈上的对象来管理申请的资源(堆空间、套接字、文件描述符、锁...),在对象出作用域时自动调用析构函数将申请的资源释放

使用RAII的例子

智能指针

智能指针就使用了这种思想,下面实现一个最简单的智能指针:

template<class T>
class SmartPtr //使用类来封装资源
{
public:
	SmartPtr(T* ptr):_ptr(ptr)
	{}
	~SmartPtr()
	{
		delete _ptr;
		cout << "delete->" << _ptr << endl;
	}
private:
	T* _ptr;//被管理的堆空间指针
};
int main()
{
	SmartPtr<int> ptr(new int(2));//ptr对象的生命周期和堆资源的生命周期绑定了
	return 0;
}

这样简单的智能指针,在对象析构时会自动调用delete释放申请的堆空间。这样就消除了忘记释放堆空间而导致内存泄露的危害。


文件资源

同样使用RAII思想还可以管理其他需要手动释放的资源,比如打开的文件:

#include<iostream>
#include<fstream>
using namespace std;
class SmartFile
{
public:
	SmartFile(const char* path)
	{
		_filehandle.open(path, ios::out);
	}
	fstream& gethandle()
	{
		return _filehandle;//获取句柄,方便对文件进行操作
	}
	~SmartFile()
	{
		_filehandle.close();//对象生命周期结束自动关闭文件
		cout << "file closed" << endl;
	}
private:
	fstream _filehandle;//文件句柄
};

int main()
{
	SmartFile file("1.txt");
	char buf[] = "sas";
	file.gethandle().write(buf,strlen(buf));
}

在linux中管理锁资源
#include<pthread.h>
class mutexguard
{
public:
    mutexguard(pthread_mutex_t * mutex) :_mutex(mutex)
	{
		pthread_mutex_lock(_mutex);//加锁
	}
    ~mutexguard()
    {
		pthread_mutex_unlock(_mutex);//解锁
    }
private:
	pthread_mutex_t * _mutex;
};
int main()
{
	pthread_mutex_t _mutex;
	pthread_mutex_init(&_mutex, nullptr);
	mutexguard(&mutex);
	return 0;
}

上面所实现的最简单的智能指针存在拷贝和赋值的问题,例如:

int main()
{
	SmartPtr<int> ptr1(new int(2));
	SmartPtr<int> ptr2 = ptr1;//拷贝给ptr2,让ptr2也可以访问同一个指针
	return 0;
}

此时程序一旦运行,立马崩溃

原因是此时智能指针这个类没有实现拷贝构造函数,只有默认的拷贝构造:对内置类型按字节浅拷贝。使得ptr1和ptr2内管理的指针指向同一块堆空间

当出作用域时,两个对象都调用析构函数,使得同一块堆空间被释放两次,程序崩溃。


如何解决这个问题?C++的发展过程出现了多种解决方式如下:

智能指针发展历程

auto_ptr

初代智能指针,当拷贝智能指针时,会将智能指针的所有权转移。

auto_ptr<int> ptr1(new int(2));//ptr1->new int(2)
auto_ptr<int> ptr2=ptr1; //ptr2->new int(2)  ptr1->nullptr

缺点

没有解决需求:如果有人想同时使用ptr1,ptr2管理同一指针是无法做到的,且如果不了解管理权转移的特性还会出现访问空指针的错误。 

不支持数组:auto_ptr默认指向的是一个对象,如果指向的是一块由多个对象组成的连续堆空间,释放时智能释放第一个对象。(定制删除器来解决:传入可调用对象来释放空间)

unique_ptr

它是c++11引用的一个智能指针:它拥有其指向对象的独占所有权,即只可以有一个智能指针指向要管理的指针(暴力方法)。通过私有拷贝构造、赋值函数来禁止unique_ptr的拷贝和赋值

缺点:还是没有解决同时用多个个智能指针管理同一个指针的需求。

shared_ptr

使用引用计数的思想来实现,解决了用多个智能指针管理同一指针的需求。

引用计数:使用计数器来统计指向资源的指针个数,新增指针指向时,计数器++,有指针移除时,计数器--,如果计数器减到0,表明已经没有指针指向这块资源了,即马上将资源释放

下面实现一个简单的shared_ptr

template<class T>
class shared_ptr
{
public:
	shared_ptr(T* ptr) :_ptr(ptr), _cnt(new int(1))//新的智能指针指向新资源,默认计数器为1
	{}
	shared_ptr(const shared_ptr& sp)//拷贝构造
	{
		//指向相同,计数器++
		_ptr = sp._ptr;
		_cnt = sp._cnt;//两智能指针使用同一计数器
		(*_cnt)++;
	}
	void del()
	{
		if (--(*_cnt) == 0)//智能指针解除对一个堆空间指向时,要判断计数器是否为0,为0才delete
		{
			cout << "delete->" << _ptr << endl;
			delete _ptr;
            delete _cnt;
		}
	}
	shared_ptr& operator=(const shared_ptr& sp)//赋值运算符重载
	{
		if (sp._ptr != _ptr)//保证指向相同的智能指针不会给自己赋值
		{
			del();//左边指向资源指针--
			_ptr = sp._ptr;
			_cnt = sp._cnt;
			*(_cnt)++;//右边指向资源指针++
		}
		return *this;
	}
	~shared_ptr()
	{
		del();
	}
	//重载*,->使智能指针像指针一样使用
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;//所管理的指针
	int* _cnt;//计数器
};

使用以下代码测试 

 结果:

shared_ptr设计非常优雅,可用性强,但在实际开发过程中发现了另一问题:循环引用

循环引用的分析和解决:

循环引用是指管理一块堆空间的智能指针存在于另一块堆空间,依赖于另一块堆空间的释放这块堆空间才会释放;而管理另一块堆空间的智能指针又在这一块堆空间上,依赖于这一块堆空间的释放。

一旦形成这种关系,两块堆空间都将无法释放,造成内存泄露!

解决方法:在堆内部创建智能指针shared_ptr时,改为创建weak_ptrweak_ptr只有指向的作用,不会占用引用计数而导致堆空间无法释放

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无极太族

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值