C++11中异常处理机制详解

2022-11-13 17:11:24 异常 机制 详解

一、异常的引入

传统的C语言处理异常的方式有两种:

1.终止程序:使用assert断言语句,如果发生内存错误等,比如内存泄漏或者除0错误,都会直接终止程序。

2.返回错误码:通过错误码判断发生的异常的类型是什么,如系统的很多库的接口程序通过把错误码放到errno中,表示错误。

在实际中的C语言程序基本都是通过返回错误码的方式来处理错误的,部分情况下使用终止程序来处理比较严重的错误。

二、C++异常的关键字

目前市面上的大部分的主流语言都是使用异常机制来处理错误的,当一个函数发现自己无法处理一些错误的时候会进行抛异常处理,让函数直接跳转到捕获异常的地方去处理异常。

下面介绍C++处理异常的几个关键字:

throw:当问题出现的时候,程序会抛出一个异常,这是通过throw关键字来完成的。

catch:通过异常处捕获异常,catch块主要用于处理异常,或者执行一些其他的操作。

try:try块中的代码标识将被激活的特定异常,它的后面一般会跟一个catch块。

三、异常的抛出与处理规则

double Disvision(int a, int b)
{
    if (b == 0)
    {
        throw  "Disvision by zero condition";
    }
    else
    {
        return ((double)a / (double)b);
    }
}
void Func()
{
    int len, time;
    cin >> len >> time;
    cout << Disvision(len, time) << endl;
}
int main()
{
    try 
    {
        Func();
    }
    catch(const char* errmsg)
    {
        cout << errmsg << endl;
    }
    catch (...)
    {
        cout << "unkown exception" << endl;
    }
    return 0;
}

当向time传入的值为0的时候,调用throw抛出异常,catch会捕获到该异常进行处理。

1.异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪一个catch的处理代码。

2.被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的一个。

在这段程序中,有一个调用链:main()->Func()->Disvision(),在Disvision中抛出异常就会沿着调用链寻找能捕获异常的catch来执行。假设Func()中有一个与main()中一模一样的catch函数,那么除0的异常就会优先调用Func()中的catch,因为它离着最近。

3.catch的异常其实是抛出对象的拷贝,因为真正抛出的对象在出作用域的时候就已经被销毁了,拷贝的对象在catch成功之后也会自动销毁。

4.catch(…)可以捕获任意类型的异常,但问题是不知道异常错误是什么。

5.实际中抛出和捕获的匹配原则有一个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获。(要引入多态)

6.当catch异常之后,会沿着catch后的子句继续执行,类似Goto,算是异常的一个缺陷。

四、异常缺陷的处理

double Disvision(int a, int b)
{
    if (b == 0)
    {
        throw  "Disvision by zero condition";
    }
    else
    {
        return ((double)a / (double)b);
    }
}
void Func()
{
    int* array = new int[10];
    int len, time;
    cin >> len >> time;
    cout << Disvision(len, time) << endl;
    delete[] array;//一旦抛出异常,这里就不会被执行
}
int main()
{
    try 
    {
        Func();
    }
    catch(const char* errmsg)
    {
        cout << errmsg << endl;
    }
    catch (...)
    {
        cout << "unkown exception" << endl;
    }
    return 0;
}

对于这段代码而言,一旦Division抛出异常就会立刻执行主函数中的catch,delete释放内存就不会被执行。因此我们需要对这种情况进行处理,即在Func中也捕获一次异常,但是不对异常进行处理,只是释放空间:

void Func()
{
    int* array = new int[10];
    int len, time;
    cin >> len >> time;
    try {
        cout << Disvision(len, time) << endl;
    }
    catch (const char* errmsg)
    {
        cout << "释放空间" << endl;
        delete[] array;
        throw;
    }
}

在释放空间之后,直接调用throw表示将捕获到的异常再抛出去。

五、自定义异常体系

class Exception
{
public:
	Exception(const string& errmsg, int id)
		:_errmsg(errmsg)
		, _id(id)
	{}

	virtual string what() const
	{
		return _errmsg;
	}
protected:
	string _errmsg;
	int _id;
};

class sqlException : public Exception
{
public:
	SqlException(const string& errmsg, int id, const string& sql)
		:Exception(errmsg, id)
		, _sql(sql)
	{}

	virtual string what() const
	{
		string str = "SqlException:";
		str += _errmsg;
		str += "->";
		str += _sql;
		return str;
	}

private:
	const string _sql;
};

class CacheException : public Exception
{
public:
	CacheException(const string& errmsg, int id)
		:Exception(errmsg, id)
	{}

	virtual string what() const
	{
		string str = "CacheException:";
		str += _errmsg;
		return str;
	}
};

class httpserverException : public Exception
{
public:
	HttpServerException(const string& errmsg, int id, const string& type)
		:Exception(errmsg, id)
		, _type(type)
	{}

	virtual string what() const
	{
		string str = "HttpServerException:";
		str += _type;
		str += ":";
		str += _errmsg;
		return str;
	}

private:
	const string _type;
};

void SQLMgr()
{
	srand(time(0));
	if (rand() % 7 == 0)
	{
		throw SqlException("权限不足", 100, "select * from name = '张三'");
	}

	//throw "xxxxxx";
}

void CacheMgr()
{
	srand(time(0));
	if (rand() % 5 == 0)
	{
		throw CacheException("权限不足", 100);
	}
	else if (rand() % 6 == 0)
	{
		throw CacheException("数据不存在", 101);
	}

	SQLMgr();
}

void HttpServer()
{
	// ...
	srand(time(0));
	if (rand() % 3 == 0)
	{
		throw HttpServerException("请求资源不存在", 100, "get");
	}
	else if (rand() % 4 == 0)
	{
		throw HttpServerException("权限不足", 101, "post");
	}
	CacheMgr();
}

void ServerStart()
{
	while (1)
	{
		this_thread::sleep_for(chrono::seconds(1));//休眠1s
		try {
			HttpServer();
		}
		catch (const Exception& e) // 这里捕获父类对象就可以
		{
			// 多态
			cout << e.what() << endl;
		}
		catch (...)
		{
			cout << "Unkown Exception" << endl;
		}
	}
}
int main()
{
	ServerStart();
	return 0;
}

这里使用随机数进行模拟异常的抛出类型,我们使用父类捕获异常,抛出子类异常,使用多态调用子类中的what()函数。

六、异常规范

1.异常规格说明的目的是为了让函数使用者知道。可以在函数的后面接throw类(类型),列出这个函数可能抛的所有异常类型。

2.函数后面接throw(),表示函数不抛异常。

3.若无异常接口声明,则此函数可以抛掷任何类型的异常。

void func() throw(A, B, C, D);//只会抛A/B/C/D中的某种类型的异常
void* operator new(size_t size) throw(bad alloc);//这里表示这个函数只会抛出bad_alloc的异常
void* operator new(size_t size, void* ptr) throw();//这个函数不会抛异常

在C++11中,引入了noexception

bool Compare(int x, int y) noexcept(noexcept(x > y))  //C++11
{
    return x > y;//表示如果x > y不发生异常,则Compare函数不会发生异常。
}

七、异常安全

构造函数完成对象的初始化,最好不要在构造函数中抛异常,否则可能导致对象不完整,或没有完全初始化。

析构函数主要完成资源的清理,最好不要在析构函数中抛异常,否则可能导致资源泄漏(内存泄漏,句柄未关闭)。

C++异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄漏,在lock和unlock之间抛出了异常导致死,C++经常使用RAII来解决以上问题,关于RAII我们在智能指针中讲解。

八、异常的优缺点

1.优点

1.相比错误码,更加清晰展示出错信息。

2.很多库中包含异常,boost,gtest,gmock等常用的库,使用它们也需要异常。

3.部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码方式处理,比如T&operator这样的函数,只有一个返回值,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误。

2.缺点

1.异常类似goto,会导致程序的执行流乱跳,并且非常混乱。

2.C++没有垃圾回收机制,可能导致内存泄漏。

3.各个公司的异常体系不同,有一定的学习成本。

到此这篇关于C++11中异常处理机制详解的文章就介绍到这了,更多相关C++11异常处理内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

相关文章