57、C++ 异常处理:嵌套块与类对象的深入探索

C++ 异常处理:嵌套块与类对象的深入探索

1. 异常处理的基本概念

在程序运行过程中,异常是不可避免的。异常处理机制允许我们捕获和处理这些意外情况,从而增强程序的健壮性。在 C++ 中,我们可以使用 try catch 块来实现异常处理。在不同的 try 块中调用函数时,对应的 catch 块会处理该函数抛出的特定类型的异常。我们可以根据程序的结构和操作需求,选择在最方便的层级处理异常。极端情况下,我们可以在 main() 函数中捕获程序中任何地方出现的异常,只需将 main() 函数的代码放在 try 块中,并添加合适的 catch 块。

2. 嵌套 try

try 块可以嵌套在另一个 try 块内部。每个 try 块都有自己的 catch 块集合,用于处理该块内部可能抛出的异常。 catch 块仅对其所属 try 块内抛出的异常起作用。以下是嵌套 try 块的处理流程:

graph TD;
    A[外层 try 块] --> B{是否抛出异常};
    B -- 是 --> C{异常类型};
    C -- int --> D[外层 catch(int)];
    C -- long --> E[外层 catch(long)];
    A --> F[内层 try 块];
    F --> G{是否抛出异常};
    G -- 是 --> H{异常类型};
    H -- int --> I[内层 catch(int)];
    H -- long --> J{内层无匹配 catch};
    J --> E;

下面是一个嵌套 try 块并在函数内抛出异常的示例代码:

// Ex15_02.cpp
// Throwing exceptions in nested try blocks
#include <iostream>

void throwIt(int i)
{
  throw i;                             // Throws the parameter value
}

int main()
{
  for(int i {} ; i <= 5 ; ++i)
  {
    try
    {
      std::cout << "outer try:\n";
      if(i == 0)
        throw i;                       // Throw int exception

      if(i == 1)
        throwIt(i);                    // Call the function that throws int

      try
      {                                // Nested try block
        std::cout << " inner try:\n";
        if(i == 2)
          throw static_cast<long>(i);  // Throw long exception

        if(i == 3)
          throwIt(i);                  // Call the function that throws int
      }                                // End nested try block
      catch(int n)
      {
        std::cout << " Catch int for inner try. " << "Exception " << n << std::endl;
      }

      std::cout << "outer try:\n";
      if(i == 4)
        throw i;                       // Throw int
      throwIt(i);                      // Call the function that throws int
    }
    catch(int n)
    {
      std::cout << "Catch int for outer try. " << "Exception " << n << std::endl;
    }
    catch(long n)
    {
      std::cout << "Catch long for outer try. " << "Exception " << n << std::endl;
    }
  }
}

该程序的输出如下:

outer try:
Catch int for outer try. Exception 0
outer try:
Catch int for outer try. Exception 1
outer try:
inner try:
Catch long for outer try. Exception 2
outer try:
inner try:
Catch int for inner try. Exception 3
outer try:
Catch int for outer try. Exception 3
outer try:
inner try:
outer try:
Catch int for outer try. Exception 4
outer try:
inner try:
outer try:
Catch int for outer try. Exception 5

代码解释:
- throwIt() 函数用于抛出传入的参数值。如果在 try 块外部调用该函数,程序会立即终止,因为异常未被捕获,默认的终止处理程序将被调用。
- 所有异常都在 for 循环内抛出。通过检查循环变量 i 的值,我们可以决定何时抛出异常以及抛出何种类型的异常。
- 当 i 为 0 时,外层 try 块抛出 int 类型的异常,由外层对应的 catch 块捕获。
- 当 i 为 1 时,调用 throwIt() 函数抛出 int 类型的异常,同样由外层的 catch 块捕获。
- 当 i 为 2 时,内层 try 块抛出 long 类型的异常,由于内层没有匹配的 catch 块,该异常传播到外层,由外层的 catch(long) 块处理。
- 当 i 为 3 时,内层 try 块调用 throwIt() 函数抛出 int 类型的异常,由内层的 catch(int) 块捕获。之后,外层 try 块再次调用 throwIt() 函数抛出 int 类型的异常,由外层的 catch(int) 块捕获。
- 当 i 为 4 和 5 时,外层 try 块抛出 int 类型的异常,由外层的 catch(int) 块捕获。

3. 类对象作为异常

在 C++ 中,我们可以将任何类型的类对象作为异常抛出。通常,我们会定义特定的异常类来表示特定的问题。异常类对象通常包含一个描述问题的消息,可能还有某种错误代码。以下是一个简单的异常类定义:

// MyTroubles.h Exception class definition
#ifndef MYTROUBLES_H
#define MYTROUBLES_H
#include <string>
using std::string;

class Trouble
{
private:
  string message;
public:
  Trouble(string str = "There's a problem") : message {str} {}
  string what() const {return message;}
};
#endif

下面是一个抛出异常类对象的示例代码:

// Ex15_03.cpp
// Throw an exception object
#include <iostream>
#include "MyTroubles.h"

int main()
{
  for(int i {}; i < 2 ; ++i)
  {
    try
    {
      if(i == 0)
        throw Trouble {};
      else
        throw Trouble {"Nobody knows the trouble I've seen..."};
    }
    catch(const Trouble& t)
    {
      std::cout << "Exception: " << t.what() << std::endl;
    }
  }
}

该程序的输出如下:

Exception: There's a problem
Exception: Nobody knows the trouble I've seen...

代码解释:
- 在 for 循环中,当 i 为 0 时,抛出一个使用默认构造函数创建的 Trouble 对象;当 i 为 1 时,抛出一个包含特定消息的 Trouble 对象。
- catch 块的参数是一个引用,这是因为异常对象在抛出时会被复制,如果不使用引用,会进行不必要的二次复制。异常对象抛出时,首先会复制创建一个临时对象,原对象会因为退出 try 块而被销毁,复制的对象会传递给 catch 处理程序。

4. 匹配 catch 处理程序与异常

try 块后面的 catch 处理程序会按照代码中出现的顺序进行检查,第一个参数类型与异常类型匹配的处理程序将被执行。对于基本类型的异常,需要与 catch 块中的参数类型完全匹配;对于类对象异常,可能会进行隐式类型转换以匹配处理程序的参数类型。以下是匹配规则:
- 忽略 const 修饰符后,参数类型与异常类型相同。
- 参数类型是异常类的直接或间接基类,或者是异常类直接或间接基类的引用,忽略 const 修饰符。
- 异常和参数都是指针,并且异常类型可以隐式转换为参数类型,忽略 const 修饰符。

这些类型转换规则对 try 块的 catch 块顺序有影响。如果有多个处理同一类层次结构中不同异常类型的处理程序,最派生的类类型必须排在前面,最基类的类型排在最后。如果基类类型的处理程序排在派生类类型处理程序之前,那么基类处理程序将总是被选择来处理派生类异常,派生类类型的处理程序将永远不会被执行。

我们可以扩展之前的 MyTroubles.h 文件,添加更多的异常类:

// MyTroubles.h Exception classes
#ifndef MYTROUBLES_H
#define MYTROUBLES_H
#include <string>
using std::string;

class Trouble
{
private:
  string message;
public:
  Trouble(string str = "There's a problem") : message {str} {}

  virtual ~Trouble(){}                               // Virtual destructor
  virtual string what() const { return message; }
};

// Derived exception class
class MoreTrouble : public Trouble
{
public:
  MoreTrouble(string str = "There's more trouble...") : Trouble {str} {}
};

// Derived exception class
class BigTrouble : public MoreTrouble
{
public:
  BigTrouble(string str = "Really big trouble...") : MoreTrouble {str} {}
};

#endif

以下是抛出和捕获这些异常类对象的示例代码:

// Ex15_04.cpp
// Throwing and catching objects in a hierarchy
#include <iostream>
#include "MyTroubles.h"

int main()
{
  Trouble trouble;
  MoreTrouble moreTrouble;
  BigTrouble bigTrouble;

  for (int i {}; i < 7; ++i)
  {
    try
    {
      if (i == 3)
        throw trouble;
      else if (i == 5)
        throw moreTrouble;
      else if(i == 6)
        throw bigTrouble;
    }
    catch (const BigTrouble& t)
    {
      std::cout << "BigTrouble object caught: " << t.what() << std::endl;
    }
    catch (const MoreTrouble& t)
    {
      std::cout << "MoreTrouble object caught: " << t.what() << std::endl;
    }
    catch (const Trouble& t)
    {
      std::cout << "Trouble object caught: " << t.what() << std::endl;
    }
    std::cout << "End of the for loop (after the catch blocks) - i is " << i << std::endl;
  }
}

该程序的输出如下:

End of the for loop (after the catch blocks) - i is 0
End of the for loop (after the catch blocks) - i is 1
End of the for loop (after the catch blocks) - i is 2
Trouble object caught: There's a problem
End of the for loop (after the catch blocks) - i is 3
End of the for loop (after the catch blocks) - i is 4
MoreTrouble object caught: There's more trouble...
End of the for loop (after the catch blocks) - i is 5
BigTrouble object caught: Really big trouble...
End of the for loop (after the catch blocks) - i is 6

代码解释:
- 在 for 循环中,根据 i 的值抛出不同的异常对象。
- 每个 catch 块包含不同的消息,输出结果显示了哪个 catch 处理程序被选择来处理抛出的异常。
- 注意, catch 块的参数类型都是引用,这是为了避免对异常对象进行不必要的复制。

5. 使用基类处理程序捕获派生类异常

由于派生类类型的异常可以隐式转换为基类类型,我们可以使用一个基类处理程序捕获之前示例中抛出的所有异常。以下是修改后的代码:

// Ex15_05.cpp
// Catching exceptions with a base class handler
#include <iostream>
#include "MyTroubles.h"

int main()
{
  Trouble trouble;
  MoreTrouble moreTrouble;
  BigTrouble bigTrouble;

  for (int i {}; i < 7; ++i)
  {
    try
    {
      if (i == 3)
        throw trouble;
      else if (i == 5)
        throw moreTrouble;
      else if(i == 6)
        throw bigTrouble;
    }
    catch (const Trouble& t)
    {
      std::cout << "Trouble object caught: " << t.what() << std::endl;
    }
    std::cout << "End of the for loop (after the catch blocks) - i is " << i << std::endl;
  }
}

该程序的输出如下:

End of the for loop (after the catch blocks) - i is 0
End of the for loop (after the catch blocks) - i is 1
End of the for loop (after the catch blocks) - i is 2
Trouble object caught: There's a problem
End of the for loop (after the catch blocks) - i is 3
End of the for loop (after the catch blocks) - i is 4
Trouble object caught: There's more trouble...
End of the for loop (after the catch blocks) - i is 5
Trouble object caught: Really big trouble...
End of the for loop (after the catch blocks) - i is 6

代码解释:
- 当 catch 块的参数是基类的引用时,它可以匹配任何派生类异常。虽然输出显示捕获的是 Trouble 对象,但实际上捕获的是派生类对象。
- 当异常通过引用传递时,其动态类型会被保留。我们可以使用 typeid() 运算符获取并显示异常对象的动态类型。修改 catch 块的代码如下:

catch(const Trouble& t)
{
  std::cout << typeid(t).name() << " object caught: " << t.what() << std::endl;
}

修改后的程序输出如下:

End of the for loop (after the catch blocks) - i is 0
End of the for loop (after the catch blocks) - i is 1
End of the for loop (after the catch blocks) - i is 2
class Trouble object caught: There's a problem
End of the for loop (after the catch blocks) - i is 3
End of the for loop (after the catch blocks) - i is 4
class MoreTrouble object caught: There's more trouble...
End of the for loop (after the catch blocks) - i is 5
class BigTrouble object caught: Really big trouble...
End of the for loop (after the catch blocks) - i is 6

需要注意的是,有些编译器默认不启用运行时类型识别(RTTI),如果上述代码无法正常工作,需要检查编译器选项并启用该功能。如果将 catch 块的参数类型改为值传递,即 catch(Trouble t) ,则会丢失异常对象的动态类型信息。

通过以上内容,我们深入了解了 C++ 中异常处理的机制,包括嵌套 try 块的使用、类对象作为异常的处理以及如何匹配 catch 处理程序与异常。在实际编程中,合理使用异常处理可以提高程序的健壮性和可维护性。

C++ 异常处理:嵌套块与类对象的深入探索

6. 异常处理的最佳实践

在实际编程中,为了更好地利用 C++ 的异常处理机制,我们需要遵循一些最佳实践:
- 定义特定异常类 :如前面所述,定义特定的异常类来表示不同类型的问题,这样可以使异常处理更加清晰和易于维护。例如,在处理文件操作时,可以定义 FileOpenError FileReadError 等异常类。
- 异常类层次结构 :使用继承来创建异常类的层次结构,最派生的类表示最具体的问题,基类表示更通用的问题。这样可以方便地使用基类处理程序捕获多个相关的异常。
- catch 块顺序 :按照从最具体到最通用的顺序排列 catch 块,确保派生类异常首先被处理,避免基类处理程序掩盖派生类异常。
- 使用引用参数 :在 catch 块中使用引用参数,避免不必要的对象复制,同时保留异常对象的动态类型信息。
- 避免在异常类成员函数中抛出异常 :为了保持异常处理逻辑的可管理性,异常类的成员函数应避免抛出异常。

7. 异常处理的性能考虑

虽然异常处理为程序提供了强大的错误处理能力,但也会带来一定的性能开销。以下是一些性能相关的考虑:
- 异常抛出和捕获的开销 :抛出和捕获异常涉及到栈展开(stack unwinding)过程,即从异常抛出点开始,依次销毁栈上的对象,直到找到匹配的 catch 块。这个过程会消耗一定的时间和资源。
- 异常对象的复制 :异常对象在抛出时会被复制,这也会带来一定的性能开销。因此,建议在 catch 块中使用引用参数,避免额外的复制。
- 异常处理的频率 :频繁抛出和捕获异常会影响程序的性能。在设计程序时,应尽量避免不必要的异常处理,优先使用条件判断来处理可预见的错误。

8. 异常处理与资源管理

在异常处理过程中,资源管理是一个重要的问题。如果在异常抛出时,程序中分配的资源(如内存、文件句柄等)没有被正确释放,会导致资源泄漏。为了避免这种情况,可以使用 RAII(Resource Acquisition Is Initialization)技术。

RAII 是一种 C++ 编程技术,它利用对象的生命周期来管理资源。当对象创建时,资源被获取;当对象销毁时,资源被释放。以下是一个简单的 RAII 示例:

#include <iostream>
#include <fstream>

class FileHandler {
private:
    std::fstream file;
public:
    FileHandler(const std::string& filename) : file(filename, std::ios::in) {
        if (!file.is_open()) {
            throw std::runtime_error("Failed to open file");
        }
    }

    ~FileHandler() {
        if (file.is_open()) {
            file.close();
        }
    }

    std::fstream& getFile() {
        return file;
    }
};

int main() {
    try {
        FileHandler handler("example.txt");
        // 使用文件进行操作
        std::string line;
        while (std::getline(handler.getFile(), line)) {
            std::cout << line << std::endl;
        }
    } catch (const std::exception& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }
    return 0;
}

在这个示例中, FileHandler 类封装了文件操作,当对象创建时,文件被打开;当对象销毁时,文件被关闭。即使在操作过程中抛出异常, FileHandler 对象的析构函数也会确保文件被正确关闭,避免资源泄漏。

9. 异常处理的总结

异常处理是 C++ 中一项重要的特性,它可以帮助我们更好地处理运行时错误,提高程序的健壮性和可维护性。以下是对本文内容的总结:
| 要点 | 描述 |
| ---- | ---- |
| 嵌套 try 块 | 可以在一个 try 块中嵌套另一个 try 块,每个 try 块有自己的 catch 块,异常会首先由内层 try 块的 catch 块处理,如果没有匹配的处理程序,会传播到外层 try 块。 |
| 类对象作为异常 | 可以将类对象作为异常抛出,通常定义特定的异常类来表示不同的问题,异常类对象可以包含错误消息和错误代码。 |
| catch 处理程序匹配 | catch 处理程序按照代码中出现的顺序进行检查,第一个参数类型与异常类型匹配的处理程序将被执行。对于类对象异常,可能会进行隐式类型转换。 |
| 基类处理程序捕获派生类异常 | 派生类异常可以隐式转换为基类类型,因此可以使用基类处理程序捕获多个相关的异常。 |
| 最佳实践 | 定义特定异常类、使用异常类层次结构、合理安排 catch 块顺序、使用引用参数、避免在异常类成员函数中抛出异常。 |
| 性能考虑 | 异常处理会带来一定的性能开销,应尽量避免不必要的异常处理。 |
| 资源管理 | 使用 RAII 技术确保在异常处理过程中资源被正确释放,避免资源泄漏。 |

通过合理使用异常处理机制,并遵循最佳实践,我们可以编写出更加健壮、可靠的 C++ 程序。在实际开发中,需要根据具体的应用场景和需求,灵活运用异常处理技术,平衡程序的健壮性和性能。

graph LR;
    A[异常处理] --> B[嵌套 try 块];
    A --> C[类对象异常];
    A --> D[catch 匹配];
    A --> E[基类捕获派生类];
    A --> F[最佳实践];
    A --> G[性能考虑];
    A --> H[资源管理];

希望本文能帮助你深入理解 C++ 中的异常处理机制,并在实际编程中有效地运用它们。如果你有任何疑问或建议,欢迎在评论区留言。

内容概要:本文介绍了一个基于MATLAB实现的无人机三维路径规划项目,采用蚁群算法(ACO)多层感知机(MLP)相结合的混合模型(ACO-MLP)。该模型通过三维环境离散化建模,利用ACO进行全局路径搜索,并引入MLP对环境特征进行自适应学习启发因子优化,实现路径的动态调整多目标优化。项目解决了高维空间建模、动态障碍规避、局部最优陷阱、算法实时性及多目标权衡等关键技术难题,结合并行计算参数自适应机制,提升了路径规划的智能性、安全性和工程适用性。文中提供了详细的模型架构、核心算法流程及MATLAB代码示例,涵盖空间建模、信息素更新、MLP训练融合优化等关键步骤。; 适合人群:具备一定MATLAB编程基础,熟悉智能优化算法神经网络的高校学生、科研人员及从事无人机路径规划相关工作的工程师;适合从事智能无人系统、自动驾驶、机器人导航等领域的研究人员; 使用场景及目标:①应用于复杂三维环境下的无人机路径规划,如城市物流、灾害救援、军事侦察等场景;②实现飞行安全、能耗优化、路径平滑实时避障等多目标协同优化;③为智能无人系统的自主决策环境适应能力提供算法支持; 阅读建议:此资源结合理论模型MATLAB实践,建议读者在理解ACOMLP基本原理的基础上,结合代码示例进行仿真调试,重点关注ACO-MLP融合机制、多目标优化函数设计及参数自适应策略的实现,以深入掌握混合智能算法在工程中的应用方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值