我应该如何实现我的 C++ 异常?

How should I implement my C++ exceptions?

假设我有一个 C++ 库。该库在某些情况下会抛出异常。我想让这个库的用户能够简单地捕获这些异常并告诉哪里出了问题。处理它的最佳方法是什么?我提出了以下可能的解决方案:

  1. 只需使用带有自定义错误消息的标准异常 类。然而,这会让捕获异常的人感到厌烦,告诉他们哪里出了问题。
  2. 创建 std::exception 的子类并将其抛出。添加某种错误代码(可能是枚举?或宏?)以便用户可以检查出了什么问题。
  3. Create multiple exception sub类: 每个可能的场合都可以抛出一个异常。这似乎是个好主意,但我认为为每个可能的错误创建一个子类太过分了。

我真的不能决定。解决这个问题的正确方法是什么?

3. Create multiple exception subclasses: one for each possible occasion an exception can be thrown. This seems like a neat idea but I think it's too excessive to create a subclass for every possible error.

这个,因为你说的原因:你的用户可以准确地捕捉到他们想要捕捉的东西。

简而言之,按照预期使用异常。

2. Create a subclass of std::exception and throw that. Add some sort of error code (maybe an enum? or a macro?) so that the user can check what went wrong.

您可能希望加入这种方法的 一点点 ,其中一种语义类型的异常具有 "subcategories" 或您永远不想的其他辅助数据过滤,但您的用户可能不感兴趣。

因为程序写错而出现的异常,应该来源于std::logic_error。这方面的示例是超出范围的索引。 std::logic_error 是您不希望发生的错误,大多数程序将无法从中恢复。

可恢复的异常(即由于资源不可用而无法完成某些事情)应该来自 std::runtime_error

异常的类型应该说明出了什么问题。开发人员可能感兴趣的任何辅助信息都可以放在 what() 字符串中。如果您想向开发人员提供因果链,请考虑使用 std::throw_with_nested。这允许感兴趣的开发人员无需查看您的源代码就可以取证地发现他的操作失败的原因。

考虑使用异常层次结构。这允许您的 class 的消费者轻松地为一般故障条件编写代码,同时允许他测试重要的个别故障。

一个人为的例子:

struct failed_to_start : std::runtime_error { 
  using std::runtime_error::runtime_error; 
};

struct engine_fault : failed_to_start { 
  using std::failed_to_start::failed_to_start; 
};

struct engine_flooded : engine_fault { 
  using std::engine_fault::engine_fault; 
};

struct wrong_key : std::logic_error {
  using std::logic_error::logic_error;
};

编辑: 根据要求,使用 throw_with_nested(以及其他一些有用的技术)

的完整工作示例
#include <iostream>
#include <string>
#include <stdexcept>


enum class key {
    none, mine, yours
};

struct out_of_fuel : std::runtime_error {
    using std::runtime_error::runtime_error;
};

struct no_key : std::runtime_error {
    using std::runtime_error::runtime_error;
};

struct start_failure : std::runtime_error {
    using std::runtime_error::runtime_error;
};

struct wrong_key_error : std::logic_error {
    using std::logic_error::logic_error;
};

struct car_configuration_error : std::logic_error {
    using std::logic_error::logic_error;
};

struct fuel_tank {
    fuel_tank(double initial) : _quantity { initial } {}

    void remove_fuel(double amount) {
        using namespace std;
        if (amount > _quantity) {
            throw out_of_fuel { "fuel tank has "s
                + to_string(_quantity)
                + " litres remaining, tried to remove "s
                + to_string(amount) };
        }
        _quantity -= amount;
    }

    double _quantity = 0.0;
};

struct ignition {
    ignition(key k) : _key_type { k } {}

    void insert_key(key k) {
        if (_key_type != k) {
            throw wrong_key_error { "the wrong key was inserted" };
        }
        _current_key = k;
    }

    void turn_key() {
        if (_current_key != _key_type) {
            throw no_key { "there is no key in the ignition" };
        }
    }

    key _current_key = key::none;
    const key _key_type;
};

struct engine {
    void run() {

    }

};

struct car {
    car(key k, double initial_fuel)
    : _ignition(k)
    , _fuel_tank(initial_fuel)
    {}

    void start(key k)
    try
    {
        _ignition.insert_key(k);
        _ignition.turn_key();
        _fuel_tank.remove_fuel(1);
        _engine.run();
    }
    catch(const std::logic_error& e) {
        std::throw_with_nested(car_configuration_error { "car configuration error - please check your program" });
    }
    catch(const std::exception& e) {
        std::throw_with_nested(start_failure { "failed to start car" });
    }

    ignition _ignition;
    engine _engine;
    fuel_tank _fuel_tank;
};

void print_current_exception(int level = 0);

void print_exception(const std::exception&e, const char* prefix, int level)
{
    std::cerr << std::string(level, ' ') << prefix << ": " << e.what() << '\n';
    try {
        std::rethrow_if_nested(e);
    }
    catch(const std::exception&) {
        print_current_exception(level + 1);
    }
}

void print_current_exception(int level)
{
    auto eptr = std::current_exception();
    if (!eptr)
        return;

    try {
        std::rethrow_exception(eptr);
    }
    catch(const std::logic_error& e) {
        print_exception(e, "logic error", level);
    }
    catch(const std::runtime_error& e) {
        print_exception(e, "runtime error", level);
    }
    catch(const std::exception& e) {
        print_exception(e, "exception", level);
    }
}

int main(int argc, const char * argv[])
{
    car my_car { key::mine, .05 };
    car your_car { key::yours, 100 };

    try {
        my_car.start(key::mine);
    }
    catch(const std::exception&) {
        print_current_exception();
    }

    try {
        your_car.start(key::mine);
    }
    catch(const std::exception&) {
        print_current_exception();
    }
    return 0;
}

预期输出:

runtime error: failed to start car
 runtime error: fuel tank has 0.050000 litres remaining, tried to remove 1.000000
logic error: car configuration error - please check your program
 logic error: the wrong key was inserted