PoEdu - C++阶段班 【Po学校】 C++中的异常系统 课堂笔记
程序代码中的2种错误:
- runtime_error 编译错误,语法错误,编译器会提示。
- logic_error 运行时错误,语义错误,会发生各种各样的情况。
异常体系:
-
- throw type // 抛出
- try{} //尝试
- catch{} //捕捉
throw抛出的,不仅仅可以是数字,还可以是一个类型,或者是一个类;有抛出就要有相应的捕捉(catch),捕捉时也要注意隐匿的转换。如果不想写过多的捕捉代码,又不想放过其中任何一个异常,可以写成catch{...}
throw可以在除了析构函数之外的任何函数里面出现,甚至出现在自己写的Exception的构造函数里。
异常体系具备栈安全的机制:
抛出之前,会对栈进行清理。出一个函数的栈,就看try{} catch{}有没有处理,没有处理就清理栈,继续上抛,如此向上再次看try...catch...有没有做处理,没有就清理栈,向上抛;如此循环向上,如果到最上面还是没有try...catch....处理,就抛给编译器处理。如此所有局部变量就被销毁了;如果我们是堆上的函数异常了,就没有这些功能了。catch{}是根据栈来展开的。堆上就要程序员自己delete了。
#include <stdio.h> #include <exception> #include <iostream> #include <string> class MyExpict { public: MyExpict(const std::string &str); void What()const; private: std::string str_; }; MyExpict::MyExpict(const std::string& str):str_(str) { if (str.empty()) { throw MyExpict("str为空"); } } void MyExpict::What() const { if (str_.empty()) throw MyExpict("str_为空"); std::cout << str_ << std::endl; } class Test { public: Test(int id):id_(id) { std::cout << "Test()" << id_<<std::endl; } Test(const Test& test) { std::cout << "Test(const Test &test)" << id_<<"copy"<<std::endl; } ~Test() { std::cout << "~Test()" << std::endl; } private: int id_; }; Test& Foo4() { Test *test = new Test(2); return *test; } void Foo3() { Test test(1); Foo4(); throw MyExpict("Foo3"); } void Foo2(Test test) { Foo3(); } void Foo1(Test& test) { Foo2(test); } int main() { try { Foo1(Test(0)); } catch(MyExpict e) { e.What(); } return 0; }
运行:
Test()0
Test(const Test &test)0copy
Test()1
Test()2
~Test()
~Test()
~Test()
Foo3
最后只有new出来的Test没有释放,因为它开辟在堆空间。
自定义的异常类,在继承时一定要注意,要将派生类生成的对象写在基类对象的调用之前;一定要先catch()派生类,然后再catch()基类。
#include <stdio.h>
#include <exception>
#include <iostream>
#include <string>
class MyExpict
{
public:
MyExpict(const std::string &str);
void What()const;
private:
std::string str_;
};
MyExpict::MyExpict(const std::string& str):str_(str)
{
if (str.empty())
{
throw MyExpict("str为空");
}
}
void MyExpict::What() const
{
if (str_.empty())
throw MyExpict("str_为空");
std::cout << str_ << std::endl;
}
class MyOtherExpict:public MyExpict
{
public:
MyOtherExpict(const std::string &str);
void What()const;
private:
int id_;
};
class Test
{
public:
Test(int id):id_(id)
{
std::cout << "Test()" << id_<<std::endl;
}
Test(const Test& test)
{
std::cout << "Test(const Test &test)" << id_<<"copy"<<std::endl;
}
~Test()
{
std::cout << "~Test()" << std::endl;
}
private:
int id_;
};
Test& Foo4()
{
Test *test = new Test(2);
return *test;
}
void Foo3()
{
Test test(1);
Foo4();
throw MyOtherExpict("Foo3");
}
void Foo2(Test test)
{
Foo3();
}
void Foo1(Test& test)
{
Foo2(test);
}
int main()
{
try
{
Foo1(Test(0));
}
catch(MyExpict e)
{
e.What();
}
catch(MyOtherExpict e) //派生类的catch()要写在 基类之前
{
e.What();
}
return 0;
}
运行:
1>------ 已启动生成: 项目: ErrorTest, 配置: Debug Win32 ------
1> Error.cpp
1>e:\c_code\errortest\errortest\error.cpp(93): warning C4286: “MyOtherExpict”: 由基类(“MyExpict”)在行 89 上捕获
1>Error.obj : error LNK2019: 无法解析的外部符号 "public: __thiscall MyOtherExpict::MyOtherExpict(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const &)" (??0MyOtherExpict@@QAE@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z),该符号在函数 "void __cdecl Foo3(void)" (?Foo3@@YAXXZ) 中被引用
1>Error.obj : error LNK2019: 无法解析的外部符号 "public: void __thiscall MyOtherExpict::What(void)const " (?What@MyOtherExpict@@QBEXXZ),该符号在函数 __catch$_main$1 中被引用
1>E:\C_Code\ErrorTest\Debug\ErrorTest.exe : fatal error LNK1120: 2 个无法解析的外部命令
========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0 个 ==========
- 异常继承时需要派生类在基类之前 。
- 抛出指针:非迫不得已,不可抛出指针;任意一个指针都可以被void*捕获(catch)。
exception 是所有异常类的基类
View ColorizedCopy to Clipboardclass exception { public: exception(); exception(const char * const &message); exception(const char * const &message, int); exception(const exception &right); exception& operator=(const exception &right); virtual ~exception(); virtual const char *what() const; };
-
runtime_error 类
-
- overflow_error 溢出
- range_error 范围
- underflow_error 下溢
-
class runtime_error : public exception { public: explicit runtime_error(const string& message); explicit runtime_error(const char *message); };
-
logic_error类
-
- ivalid_argument 不存在的参数
- length_error 长度
- out_of_range 超出范围
- domain_error 领域
-
class logic_error : public exception { public: explicit logic_error(const string& message); explicit logic_error(const char *message); };
以一两类异常,只是把异常做了一个整体的划分。同级别的派生类还有:
-
bad_alloc 类 重点,用的多
class bad_alloc : public exception { bad_alloc(); virtual ~bad_alloc(); };
在具体new的场合下,在指针分配出错了,异常时用bad_alloc类
-
bad_cast类 重点,用的多
catch (bad_cast) statement
class bad_cast : public exception { public: bad_cast(const char * _Message = "bad cast"); bad_cast(const bad_cast &); virtual ~bad_cast(); };
转换时出现错误用bad_cast
异常的正确使用方法:
- 继承std::exception
- 首先catch(Myexception e),再来catch(std::exception e)
- 写在main函数中,其它的地方只写throw
抛出还可以如下图;但不建议如此写。
函数后面会抛出一个什么样的异常,这个问题很有争议,然后就延申出了另外一个“noexcept”
noexcept 表示此函数没有异常
- 冷知识:noexcept后面可以传参数,但不要这么写。
- noexcept(true)
- noexcept(false) 表示此函数会抛出异常
其实只有一个void无参的空函数,才是真正意义上的noexcept
noexcept只要了解就行
切记:永远不要在析构函数里面throw!
加一个throw()到函数中?
这是异常规范,只会出现在声明函数中,表示这个函数可能抛出任何类型的异常
- void GetTag() throw(int);表示只抛出int类型异常
-
- 并不表示一定会抛出异常,但是一旦抛出异常只会抛出int类型,如果抛出非int类型异常,调用unexsetpion()函数,退出程序。
-
- void GetTag() throw(int,char);表示抛出in,char类型异常
- void GetTag() throw();表示不会抛出任何类型异常
- void GetTag() throw(...);表示抛出任何类型异常