Menu
CoddyTech

自定义异常层次结构

本课属于 Coddy C++ 学习旅程的“面向对象编程”单元。第 82 课,共 104 课。

虽然标准库提供了 std::runtime_error 这样实用的异常类,但实际应用通常可以从创建自定义异常层次结构中受益。通过定义继承自标准异常的自定义异常类,你可以创建特定于领域的错误类型,携带更多上下文信息,并实现更精确的错误处理。

自定义异常类通常继承自 std::exception 或其某个派生类,并重写 what() 方法:

#include <exception>
#include <string>

class DatabaseException : public std::exception {
    std::string message;
public:
    DatabaseException(const std::string& msg) : message(msg) {}
    const char* what() const noexcept override { return message.c_str(); }
};

class ConnectionException : public DatabaseException {
public:
    ConnectionException(const std::string& host) 
        : DatabaseException("Failed to connect to: " + host) {}
};

class QueryException : public DatabaseException {
public:
    QueryException(const std::string& query) 
        : DatabaseException("Invalid query: " + query) {}
};

这种层次结构让 catch 块能够按不同的具体程度处理异常:

try {
    // 数据库操作……
    throw ConnectionException("localhost");
} catch (const ConnectionException& e) {
    std::cout << "Connection issue: " << e.what() << "\n";
} catch (const DatabaseException& e) {
    std::cout << "Database error: " << e.what() << "\n";
} catch (const std::exception& e) {
    std::cout << "General error: " << e.what() << "\n";
}

what() 上的 noexcept 指定符很重要,它保证该方法不会抛出异常,这是基类接口所要求的。按从最具体到最一般的顺序排列 catch 块,因为 C++ 会使用第一个匹配的处理程序。

challenge icon

挑战

简单

让我们构建一个文件处理系统,并使用自定义异常层次结构处理不同类型的文件相关错误。你将创建特定领域的异常类,让它们继承一个基类异常,从而能够按不同的具体程度精确处理错误。

你将把代码组织在三个文件中:

  • FileExceptions.h:为文件操作定义自定义异常层次结构。

    创建一个继承自 std::exception 的基类 FileException。它应存储错误消息,并使用 noexcept 限定符重写 what() 方法。

    然后创建两个派生异常类:

    • FileNotFoundException:接收文件名,并创建一条消息:File not found: [filename]
    • PermissionDeniedException:接收文件名,并创建一条消息:Permission denied: [filename]

    每个派生类都应使用相应的消息调用父类构造函数。

  • FileProcessor.h:定义一个 FileProcessor 类,用于模拟文件操作并抛出自定义异常。

    你的 FileProcessor 应包含一个方法 openFile(const std::string& filename),用于模拟打开文件:

    • 如果文件名包含 missing,则抛出 FileNotFoundException
    • 如果文件名包含 locked,则抛出 PermissionDeniedException
    • 否则,返回字符串 Successfully opened: [filename]
  • main.cpp:从输入中读取一个文件名。

    创建一个 FileProcessor,并尝试打开文件。使用 try-catch 语句块,并按从最具体到最通用的顺序编写处理程序:

    1. 捕获 FileNotFoundException 并打印:File Error: [message]
    2. 捕获 PermissionDeniedException 并打印:Access Error: [message]
    3. 捕获 FileException 并打印:General File Error: [message]

    如果没有抛出异常,就打印 openFile() 返回的成功消息。

例如,输入 report.txt 时:

Successfully opened: report.txt

输入 missing_data.csv 时:

File Error: File not found: missing_data.csv

输入 locked_config.ini 时:

Access Error: Permission denied: locked_config.ini

这个挑战展示了自定义异常层次结构如何帮助你按合适的详细程度处理错误:需要精确处理时捕获具体异常,或者需要统一处理所有文件错误时捕获基类异常。

亲手试试

#include <iostream>
#include <string>
#include "FileExceptions.h"
#include "FileProcessor.h"

using namespace std;

int main() {
    string filename;
    cin >> filename;
    
    FileProcessor processor;
    
    // TODO: 使用 try-catch 块处理异常
    // 按从最具体到最一般的顺序排列异常处理代码:
    // 1. 捕获 FileNotFoundException,输出:"File Error: [message]"
    // 2. 捕获 PermissionDeniedException,输出:"Access Error: [message]"
    // 3. 捕获 FileException,输出:"General File Error: [message]"
    // 如果没有异常,输出 openFile() 返回的成功消息
    
    try {
        // TODO: 调用 processor.openFile() 并处理结果
        
    }
    // TODO: 按从最具体到最一般的顺序添加 catch 块
    
    return 0;
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C++ 在线编译器