【c++修行之路】异常

前言

大家好久不见,今天一起来学习一下c++中的异常。

C语言C++对比

C语言在处理错误的时候一般使用assert或者错误码来处理,但二者都相对有局限性
assert:错误自动终止程序,处理方式非常暴力
错误码:只有一个错误码,错误信息相对少,不直观

C++语言提供了一种新的方式解决上面的问题,即异常

异常

throw在可能出现异常的地方使用,意为抛出异常;try/catch在处理异常的地方使用,意为处理异常。

实例代码:

#include <iostream>

using namespace std;

double Div()
{
    
    
	int x, y;
	cin >> x >> y;

	if (y == 0)
		throw "div Exception cause div 0!!!";
	else
		return (double)x / (double)y;
}

int main()
{
    
    
	try
	{
    
    
		Div();
	}
	catch (const char* str)
	{
    
    
		cout << "[Exception]:" << str << endl;
	}

	return 0;
}

捕获异常时的注意事项

匹配

1、类型必须匹配,必须严格匹配。
2、在最近的捕获处抛出。

第二点:比如在此处,抛出时离middle最近且严格匹配,所以直接在middle层抛出了。

#include <iostream>

using namespace std;

double Div()
{
    
    
	int x, y;
	cin >> x >> y;

	if (y == 0)
		throw "div Exception cause div 0!!!";
	else
		return (double)x / (double)y;
}


void middle()
{
    
    
	try
	{
    
    
		Div();
	}
	catch (const char* str)
	{
    
    
		cout << "[middle-Exception]:" << str << endl;
	}
}


int main()
{
    
    
	try
	{
    
    
		middle();
	}
	catch (const char* str)
	{
    
    
		cout << "[main-Exception]:" << str << endl;
	}

	return 0;
}

细节

异常抛出的时候相当于传值返回,因此捕获到的是异常的一份拷贝构造,因为编译器有很多优化,所以在抛出异常的时候一般都直接抛出一个匿名对象,捕获到的异常是一个局部对象,而不是一个临时对象这点要分清楚。

使用一个Exception类丰富错误信息

使用字符串来表示异常也有很多局限,一般在存储异常信息的时候都是使用一个异常类:

现在我们模拟一个场景:在一个服务中,我们提供网络服务、数据库服务,如果数据库出现错误要看一下特定的sql语句。

结合继承和多态,我们能够设计出更符合实际需求的异常代码:这样也会减少乱抛异常导致系统混乱的可能。顺便一提,catch里加…表示可以捕获任意异常,尽管我们无从得知是什么异常,但可以保证程序其他功能不受限制。

class Exception
{
    
    
public:
	Exception(int errid,string errmsg)
		: _errid(errid)
		, _errmsg(errmsg)
	{
    
    

	}

	virtual string GetErrMsg() const 
	{
    
    
		return _errmsg;
	}

	int GetErrid() const 
	{
    
    
		return _errid;
	}


protected:
	int _errid;
	string _errmsg;
};


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

	}

	virtual string GetErrMsg() const override 
	{
    
    
		string msg = "SqlException:";
		msg += _errid;
		msg += "->";
		msg += _sql;
		return msg;
	}
private:
	string _sql;
};


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

	}

	virtual string GetErrMsg() const override
	{
    
    
		string msg = "CacheException:";
		msg += _errmsg;
		return msg;
	}

};


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

	}


	virtual string GetErrMsg() const override
	{
    
    
		string msg = "HttpServerException:";
		msg += "type->";
		msg += _type;
		msg += _errmsg;
		return msg;
	}
private:
	const string _type;
};

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

	cout << "调用成功" << endl;
}

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();
}

int main()
{
    
    
	while (1)
	{
    
    
		this_thread::sleep_for(chrono::seconds(1));

		try
		{
    
    
			HttpServer();
		}
		catch (const Exception& e) // 这里捕获父类对象就可以
		{
    
    
			// 多态
			cout << e.GetErrMsg() << endl;
		}
		catch (...)
		{
    
    
			cout << "Unkown Exception" << endl;
		}
	}

	return 0;
}

异常的重新抛出

在这样的场景下:假如在func函数中要记录下来每一次div的结果,但如果发现除0错误后会引发异常,导致动态内存分配的空间没有释放,因此需要在这一层捕获异常并释放空间,但我么又希望异常能在外层统一处理,所以引入了重新抛出异常的概念,代码如下:

double Div()
{
    
    
	int x, y;
	cin >> x >> y;
	

	if (y == 0)
		throw "div Exception cause div 0!!!";
	else
		return (double)x / (double)y;
}

void func()
{
    
    
	int* arr = new int[20];
	try
	{
    
    
		Div();
	}
	catch (const char* str)
	{
    
    
		delete[] arr;
		throw;
	}	
	
	delete[] arr;
}


int main()
{
    
    
	
	try
	{
    
    
		func();
	}
	catch (const char* str)
	{
    
    
		cout << str << endl;
	}


}

c++98和c++11的异常建议

在c++98中,提供了指明异常的方式,但这种方式其实非常没有用,形同虚设,因为你在抛出异常的时候可以不采用这种方式,就算采用这种方式,也会有一些其他问题。

throw([e1],[e2]) 表示可能会抛出的异常

比如:
说明不抛出异常的类仍然可以抛出并且程序不会自己终止,而指明异常的可以抛出没有指明的异常,这使得这种方式非常没用。

void Noexception() throw()
{
    
    
	throw "你好";
}

void hasexception() throw(SqlException, HttpServerException)
{
    
    
	throw "helloworld";
}



int main()
{
    
    
	try
	{
    
    
		hasexception();
	}
	catch (const char* str)
	{
    
    
		cout << str << endl;
	}
	
}

c++11中提供了一种新的异常方案:
明确不抛出异常的话,可以加上noexcept关键字,如果可能抛出异常,就不需要加此关键字。

void Noexception() noexcept
{
    
    
	throw "你好";
}

这样的写法就会直接报错。最好不要在构造函数和析构函数抛出异常,因为这样有可能会导致构造/析构不完全,从而引发一系列的问题。像这样的场景还包括文件操作、上锁解锁等。

异常的优缺点

其实我们能够感受到异常带来的诸多便利,简单介绍一些:
1、使得错误信息更加清晰和丰富,一目了然。
2、相比于错误码需要层层返回错误码,异常会直接跳到最外面捕获的位置。
3、有些特殊场景只能抛出异常,比如有的函数没有返回值,容器越界等(数组越界是未定义的行为,不会抛出异常) 没有返回值的场景。

但有利必有弊,异常也有一些缺点:
1、执行流乱跳,这让我们在断点调试时非常难受。
2、库的异常体系不好,导致大家经常各用各的,很难统一。
3、需要我们规范使用,但这种规范又不强制,导致形同虚设。
4、c++没有垃圾回收机制,很容易出现内存泄漏和死锁等安全问题。

但总的来说,异常的利大于弊,是未来语言处理错误的趋势,我们仍然要学好异常。

结语

今天的内容就到这里了,我们下次再见~。

猜你喜欢

转载自blog.csdn.net/m0_73209194/article/details/131526523