前言

          如下面几张图,带你迅速理解,系统编程中获取异常终止信息,用户自定义退出码,来获取退出信息

系统编程获取进程异常终止信息

用户自定义退出码,避免进程真正执行异常代码 导致异常退出

问题是 ? 不标准,不够用

        嗯... 当访问无效内存,或者调用终止abrt等等产生信号让进程退出信息,但是大部分我们能拿到的是退出码,又或者用户自定义退出码依然是一个数字,要么查表,要么用户也可以打印一下在异常位置判断不要真正让进程执行异常,但是这依旧是不够用。

        1. 不灵活 2. 不够标准 大家都拿着自己的退出码 自己的方式来规避异常退出,不优雅,在很多大型项目里面,根据提前规避异常的位置,比如/0 或者空指针等等我们都进来规避异常的发送,因为一旦发送异常退出很多有效信息是可能被我们无法追踪的,所以提前规避!是很有必要的: 总结一下思路:        

              C++11也好又或者其他的语言 本质上异常的核心就如下情况 : 必要让x/0真正发生了导致进程异常退出,而是提前规避,这就是异常机制,只不过下面这样的写法如退出码以及使用方式不够规划,所以c++11 对于异常机制有一套面向对象的方式。

        对了实际生产中: 日志和异常都是很重要的,只不过异常也有自己的缺点哈(跳跃执行流) 但是对于定为错误,避免进程异常退出还是很重要的哈!

int divide(int x,int y){

    if(y==0){
            
        print("除0错误");// 除0错误打印一下
        exit(-1);// 让进程退出返回退出码为-1 
           //
    }
return x/y;


}

一. try 和throw 以及catch关键字基本使用 

        可以参考如下代码进行迅速上手c++的异常类型,我这里做补充:

     在c++中 你的throw关键字可以抛出任意类型的对象们可以是自定义类型,也可以是内置类型如 int const char* 等等 ,但是要注意,这跟异常体系是这样的: 

        try{    //// 内部抛出你的异常体系 } 

       catch(捕获类型1 ) {   ////   }

       catch(捕获类型2 ) {   ////   }  .... 

  如果一种所有当前try类型都不会不到你抛出的很有可能就会直接终止,因为如果throw没有人捕获他,他就调用abrt()函数 终止进程(还有可能调用栈展开也可能捕获哦!)

 所以可以用这跟 

catch(...) {}   三个点 表示可以捕获任意类型 

class myclass {


public:
	void test()const {
		cout << "我是一个测试异常类" << endl;
	}
	int _a;
	string _b;
};

void test_use1() {

	//throw; 空抛出直接报错哈
	// 这样要注意throw只会在他的一个try -catch模块中
	// 不会去别人的try-catch模块 如果没有人捕获他就直接报错了

	try {

		//受到检测的代码 这里主动抛异常
		//主动调用 throw +基类类型或者自定义类型
		//throw 'a'; 表示我抛出一个异常对象 类型是 char
		throw myclass();// 抛出一个匿名对象 类型是 myclass
	}
	catch (int a) {
		// 表示处理捕获到别人throw的 int 
	}
	catch (char b)
	{
		// 表示处理捕获到别人throw的 char 
	}
	catch (const myclass& obj) {
		// 捕获 一个myclass的类引用 const的 注意我们可以传引用哈
		// 前面的内置类型我都是值拷贝的哈
		// 捕获到之后你要是不要再对外抛出throw就表示处理完成
		obj.test();
	}
	catch (...) {
		cout << "捕获都无效类型 " << endl;
		exit(-1);
	}

}

int main() {

	test_use1();

	return 0;

}

catch(...) 表示捕获任意类型!

退出码

二. 抛异常的原理以及调用栈展开

          基本使用已经提到了,就是c++编译器三个新的关键字 ,try{   } - catch{ } 模块 在try模块中 throw 任意 类型,然后在try 所有一一对应的类型进行就近匹配原则,捕获到了然后进行处理,如果在catch模块中没有继续向外抛出就没有关系啦 .

2.1 同一try下不能在catch相同类型

try {

	throw myclass();
}
catch (myclass&obj) {

	  // 捕获到了我们不处理直接抛出
	throw;// 直接抛出表示将捕获到的对象直接抛出
}
// 同一try下捕获相同类型会报错
catch (int) {
	//捕获int 不处理 
}
catch (int) {
	 // 在封装一个捕获int对象
}

2.2 try模块下栈展开 

     在异常体系中抛出的异常对象是由throw决定的,要始终记住,在当前的try-catch中只会被捕获一次,如果当前try -catch中你无法捕获会触发栈展开,他会回到你的调用栈中,注意后面的代码都不会执行了,这就是执行流跳跃,然后检测调用栈检测也没有try -catch如果在你的调用栈能捕获那就捕获报错,否则报错。

2.2.1 图解栈展开

 图解异常的栈展开:   如下图:  当我们现在有三个函数层层嵌套的函数调用,最终在第三个函数throw了一个函数,但是很离谱的是,他自己当前的try-catch模块无法捕获他自己的抛出的异常对象,2. 所以他只能跳跃执行流注意是跳跃,直接回到调用当前函数的调用函数栈 func2  ,也就是下面的代码直接不执行了,就好像"我就倔,我今天非要解决你这问题",然后回到func2 ,但是注意哈来到func2 ,必须保证调用func3的函数调用也在一个try -catch中

然后在这个try-catch中寻找,最后发现没有解决,有展开调用栈最后回到func1 ,func1带哦有的func2的try-catch最终捕获了。

        我图中说如果func1没有捕获到就直接报错了是不严谨的哈,因为func1可能也被其他函数调用,最终栈展开哈,回到主函数! 如果主函数都没有捕获这个异常对象,最终异常处理机制就会调用abrt()终止当前进程。

2.2.2 throw 直接原地抛出当前对象

        如下代码:   简单来说,当我们这里抛出了myclass对象当前try-catch确实捕获了,但是他直接throw 这就表示对当前捕获的对象不处理原地抛出

        goood 所以再次栈展开最终输出结果你猜猜,下文有输出:

class myclass {


public:
	void test()const {
		cout << "我是一个测试异常类" << endl;
	}
	int _a;
	string _b;
};


void test_use2() {


	try {

		throw myclass(); // 注意抛出int类型 
	}
	catch (myclass&obj) {

		  // 捕获到了我们不处理直接抛出
		throw;// 直接抛出表示将捕获到的对象直接抛出
	}
	catch (string str) {
		cout << "hello i catch you:" << str;
	}
}

int main() {
	try {
		test_use2();
	}
	catch(myclass&obj){
		
		cout << "在main中捕获到obj" << endl;
	}
	catch (...) {
		cout << "在主函数捕获到异常类型" << endl;
	}
	return 0;
}

2.2.3 c++异常导致的执行流跳跃

      异常处理千般好,执行流跳跃,同如大坑的goto语句,执行流跳跃问题很明显:

 倘若一不小心那就是内存泄露:   如下场景   : 当函数mytest2()抛出异常,后面原本要析构当前函数栈帧动态开辟的内存,delete可是你直接越过了,那太好了,直接内存泄露,呜呼哀哉,然后又展开调用栈,捕获到了,问题就是内存已经泄露了,这就是执行流跳跃的坏处。

        所以为了规避这样的详细,对应动态开辟的内存,c++中的思路是RAII ,具体来说就是使用智能指针来管理这样的情况。

class myclass {
public:
	void test()const {
		cout << "我是一个测试异常类" << endl;
	}
	int _a;
	string _b;
};

void mytest2() {

	try {
		string str = "hello wrold";
		myclass* c1 = new myclass;
		int *pa = new int[5] ;
		

		throw "出问题了";
		delete pa; // 这里就不会被调用了
delete c1;// 这里不会执行 导致内存泄露 

	}

	catch (int a) {

	}

}


void mytest1() {
        
    try{
            mytest2();
    }
    catch(string str){


     }
int main(){
   mytest1();
return 0;
}



}



三. 自定义异常规划&&C++11的异常规范标准

 3.1 自定义异常体系结构

        如上我们简单花了一个自定义异常体系的类图,原因是因为,在捕获的时候,catch(基类) 是可以捕获子类元素的,这就是多态嘛,这是很关键的一步,所以我们通常在很多公司都会自定义异常体系结构,基类大概率是一个纯虚函数,内部封装一个waht方法,然后定义相关子类,我们来模拟写一个自定义的异常体系吧。

样例体系代码:

   

#include<string>
#include<thread>
#include <chrono>  
#include<Windows.h>
// 自定义异常体系结构 

class Myexception {
public:
	Myexception(const string error_message, int id)
		:_error_message(error_message)
		, _id(id) {
	}

	virtual string what()const {

		return _error_message+":"+to_string(_id);
	}
protected:
	int _id;// 表示错误id号 
	string _error_message;
};
class memory_exception :public Myexception{

public:
	memory_exception(const string error_message, int id,const string&memory_type)
	:Myexception(error_message,id),
	 _memory_type(memory_type)
	  {}
	string what() const override {
		return _error_message + ":" + to_string(_id) + _memory_type;
	 }
protected:
	string _memory_type;

};

class HttpServer_Exception :public Myexception {



public:
	// 错误码 如 404 
	HttpServer_Exception(const string& error_message, int id, const int erro) :
	 Myexception(error_message,id) ,
	  _erro(erro){
	}
	// 重写 
	string what()const override {
		return _error_message + to_string(_id) + ":" + to_string(_erro);
	}

protected:
	int _erro;

};

// 我不在函数写try-catch模块 我最终在主函数 等着用基类捕获你们
void Test_http() throw(HttpServer_Exception){
		while (1) {
			// 模拟工作 然后突然出问题 
			std::this_thread::sleep_for(std::chrono::seconds(2));
			// 休眠后抛异常
			throw HttpServer_Exception("这里发生httpsever异常", -1, 404);
		}
	}

// throw(类型1,类型2) 如果是 throw()空括号表示
// 声明当前函数不会抛出异常,否则自动调用abrt()终止进程
void Test_1() throw(){

	// .... 啥也不不做
}

void Test_memory()throw(memory_exception, HttpServer_Exception) {

	//模拟 工作 休眠1s
	std::this_thread::sleep_for(std::chrono::seconds(2));
	throw memory_exception("这里发生内存异常", -1,"空指针访问");
}




int main() {

	/// 在主函数调用 在主函数 包上try-catch即可 然后基类捕获他们 
	try {
		Test_http();
	}
	catch (const Myexception& e) {
		 
		cout << e.what()<<endl;// 在这直接捕获即可 然后输出异常信息
	}
	try {
		Test_memory();
	}
	catch (const Myexception& e) {

		cout << e.what() << endl;// 在这直接捕获即可 然后输出异常信息
	}



	//std::this_thread::sleep_for(std::chrono::seconds(2));
	cout << "hello;";
	//Sleep(10000);// windows 的是 毫秒级别的 跨平台的c++的线程库也有
	cout << "hello;";

}

3.2 C++标准异常体系结构 

基类的what字段


标准异常类图结构

理解

// 简单来说 c++的异常基类里面也包含了一个 what
// 还有一些错误信息的字段 

class exception{

public: 
   exception(const std::string&data,int id){
    _data = data; 
     _id = id;
}
    virtrul  std::string what(){
         return _data;
  }
protected:
    std::string _data;
    std:: int _id;

}

3.2 简单的使用&&简单的stl容器的异常处理

       像c++的异常有基类,其实我们也可以继承他的基类然后写自己的异常体系结构,但是生产实践当中大家都不乐意,其实c++的异常体系结构执行流的跳跃有很大的好处,就是 他会不断的展开调用栈知道回到main函数,那么我们只会可以尽量在主函数用基类捕获就就行了,也不会除太大的问题,这也就不用像错误码那样的方式要不停的自主判断调用返回。

        这也的方式也算不错,但是执行流的跳跃也会有内存的泄露问题,但是有了智能指针,其实也不算差,对于动态的内存又有异常的情况下,动态内存尽量用智能指针来指向这是一个不错的注意。

 当然你要是直接用c++异常的,那你得看人家有些容器的方法也没有保护异常判断字段,就比如vector的operator[] 就没有异常啊 如下代码:

void test_vector_e() {
	vector<int> v = {1,2,3,4,5};

	//v[10] = 2;// 这个[]方法内部没有封装 cpu检测到你越界 直接就挂了
	v.at(10); // at方法内部封装了 但是要注意在main()函数要保证异常哈 
	// 不然人家at抛出去你都不捕获。。。
}


int main() {

	try {
		test_vector_e();
	}
	catch (const exception& e) {
		cout << e.what() << endl;
	}

	return 0;
}

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐