异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者处理这个错误。 C++ 异常处理涉及到三个关键字:try、catch 和 throw。
使用 try/catch 语句的语法如下所示:
try{
// 保护代码
}
catch (ExceptionName e1) {
// catch 块
}
catch (ExceptionName e2) {
// catch 块
}
catch (ExceptionName eN) {
// catch 块
}
如果 try 块在不同的情境下会抛出不同的异常,这个时候可以尝试罗列多个 catch 语句,用于捕获不同类型的异常。
抛出异常时,可以使用 throw 语句在代码块中的任何地方抛出异常。throw 语句的操作数可以是任意的表达式,表达式的结果的类型决定了抛出的异常的类型。
异常的抛出和匹配原则:
捕获异常的例子:
double division(int a, int b)
{
if (b == 0)
{
throw "Division by zero condition!";
}
return (a / b);
}
捕获异常时,catch 块跟在 try 块后面,用于捕获异常。你可以指定想要捕捉的异常类型,这是由 catch 关键字后的括号内的异常声明决定的。例如:
try
{
// 保护代码
}
catch (ExceptionName e)
{
// 处理 ExceptionName 异常的代码
}
下面是一个实例,抛出一个除以零的异常,并在 catch 块中捕获该异常:
#include
using namespace std;
double division(int a, int b)
{
if (b == 0)
{
throw "Division by zero condition!";
}
return (a / b);
}
int main()
{
int x = 50;
int y = 0;
double z = 0;
try
{
z = division(x, y);
cout << z << endl;
}
catch (const char* msg)
{
cerr << msg << endl;
}
return 0;
}
由于我们抛出了一个类型为 const char* 的异常,因此,当捕获该异常时,我们必须在 catch 块中使用 const char*。当上面的代码被编译和执行时,它会产生下列结果:Division by zero condition!
在函数调用链中异常栈展开匹配原则:
栈展开过程如下,首先检查throm本身 是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的,则处理。没有则退出 当前函数栈,继续在调用函数的栈中进行查找,不断;重复上述过程,若到达main函数的栈,依旧没有匹配的,则终止程序
#include
using namespace std;
class Exception
{
public:
Exception(const int code, const string msg)
:_code(code)
,_msg(msg)
{}
int GetCode() const
{
return _code;
}
string Getmsg() const
{
return _msg;
}
~Exception()
{}
private:
int _code; // 错误码
string _msg; // 错误信息
};
double Division(int a, int b)
{
// 当b == 0时抛出异常
if (b == 0)
{
Exception e(1, "Division by zero condition!");
throw(e);
}
else
return ((double)a / (double)b);
}
void Func()
{
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
int main()
{
try
{
Func();
}
catch (const Exception& e)
{
cout << "错误码:" << e.GetCode() << ",错误信息:" << e.Getmsg() << endl;
}
catch (...)
{
cout << "unkown exception" << endl;
}
return 0;
}
实际使用中很多公司都会自定义自己的异常体系进行规范的异常管理,因为一个项目中如果大家随意抛异常,那么外层的调用者基本就没办法玩了,所以实际中都会定义一套继承的规范体系。这样大家抛出的都是继承的派生类对象,捕获一个基类就可以了。
class MyException : public std::exception
{
public:
MyException(const std::string& msg)
: message(msg)
{}
const char* what() const noexcept override
{
return message.c_str();
}
private:
std::string message;
};
class FileReadException : public MyException
{
public:
FileReadException(const std::string& filename)
: MyException("Error reading file: " + filename)
{}
};
class NetworkException : public MyException
{
public:
NetworkException(const std::string& host)
: MyException("Network error with host: " + host)
{}
};
void readFromFile(const std::string& filename)
{
throw FileReadException(filename);
}
void connectToServer(const std::string& host)
{
throw NetworkException(host);
}
int main()
{
try
{
readFromFile("data.txt");
}
catch (const MyException& e)
{
std::cerr << "Exception caught: " << e.what() << std::endl;
}
return 0;
}
首先,我们可以创建一个自定义的基本异常类,例如 MyException,继承自标准库的 std::exception,在基本异常类的基础上,我们可以派生出其他业务类型的异常,例如 FileReadException 和 NetworkException。
C++ 异常规范是一项 C++ 语言功能,用于指示程序员对函数可能抛出的异常类型的意图。它允许我们明确声明函数是否可以或不可以因异常而退出。编译器可以利用这些信息来优化对函数的调用,并在异常意外地离开函数时终止程序。
异常规范有两个关键字:
throw:当问题出现时,程序会抛出一个异常。我们使用 throw 关键字来实现这一点。
noexcept:在您想要处理问题的地方,通过异常处理程序捕获异常。noexcept 关键字用于指定函数不会抛出异常。
让我们来看一个简单的例子:
#include
double divide(int a, int b) noexcept
{
if (b == 0)
{
throw "Division by zero condition!";
}
return static_cast<double>(a) / b;
}
int main()
{
int numerator = 50;
int denominator = 0;
double result = 0;
try
{
result = divide(numerator, denominator);
std::cout << "Result: " << result << std::endl;
}
catch (const char* msg)
{
std::cerr << "Exception caught: " << msg << std::endl;
}
return 0;
}
在上面的代码中,我们定义了一个 divide 函数,它计算两个整数的除法。如果除数为零,我们抛出一个异常。在 main 函数中,我们使用 try 块来调用 divide 函数,并在 catch 块中捕获异常。注意,我们在 divide 函数的声明中使用了 noexcept 关键字,表示该函数不会抛出异常。
C++ 异常规范是一个有关异常处理的重要主题。它的优点和缺点如下。
优点:
缺点:
C++ 异常安全是一项关键的编程概念,旨在确保程序在发生异常时能够维持一致的状态,避免资源泄漏和数据结构破坏。
异常安全的概念:异常安全意味着当程序在异常发生时,它可以“回退得很干净”。具体而言,一个函数在发生异常时应该满足两个条件:1.不泄漏资源:已申请的资源必须被正确释放。2.不破坏数据结构:不会导致野指针等问题。
异常安全分为三个级别:
反面例子:资源泄漏:例如,互斥锁的获取和释放。如果在获取锁后发生异常,释放锁的代码将不会执行,导致资源泄漏。
数据破坏:例如,自定义类的赋值操作符重载。如果在分配新资源时抛出异常,对象的数据可能会遭到破坏。
解决方案:
资源泄漏:使用对象来管理资源,例如使用 RAII 技术或智能指针。
数据破坏:使用“拷贝并交换”策略,先创建副本,然后在副本上进行修改,最后交换资源。
总结:编写异常安全的代码需要注意资源管理和数据修改的问题,以及使用 RAII 和拷贝并交换等技术。
最佳实践:
使用 RAII 管理资源,避免资源泄漏。
注意异常发生时的回滚机制。
减少全局变量的使用,保证局部变量的异常安全性。
不知道如何处理异常时,不要捕获异常,直接终止程序。
C++标准库定义了一套异常类体系,其根部是名为 exception 的抽象基类。标准库抛出的异常都是 exception 的子类,称为标准异常(Standard Exception)。接下来深入了解一下这些异常类:
1.exception:
exception 是所有标准异常的基类。
它声明了一个 what() 虚函数,用于返回一个 const char*,表示被抛出异常的文字描述。
2.标准异常类:
这些异常类都继承自 exception,并提供了特定类型的错误信息。
一些常见的标准异常类包括:
logic_error 及其子类:表示逻辑错误,例如 std::invalid_argument、std::domain_error 等。
runtime_error 及其子类:表示运行时错误,例如 std::overflow_error、std::out_of_range 等。
3.使用标准异常:
在代码中,我们可以使用这些标准异常来处理特定的错误情况。
例如,如果发生了除以零的操作,可以抛出 std::runtime_error。
异常处理是软件开发中的重要主题,它有一些优点和缺点。
优点:
缺点:
总之,异常处理在不同项目和场景中有不同的利弊。在编写代码时,需要权衡这些因素,根据项目需求和性能要求来选择是否使用异常。