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

时间:2015-05-19 14:19:50

标签: c++ exception exception-handling shared-libraries

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

  1. 只需将标准异常类与自定义错误消息一起使用即可。然而,这会让抓住异常的人告诉他们出了什么问题很烦人。
  2. 创建std::exception的子类并抛出它。添加某种错误代码(可能是枚举?还是宏?),以便用户可以检查出错的地方。
  3. 创建多个异常子类:每个异常子类可以抛出一个异常。这似乎是一个很好的想法,但我认为为每个可能的错误创建一个子类太过分了。
  4. 我无法真正决定。什么是正确的方法?

2 个答案:

答案 0 :(得分:7)

  

3。创建多个异常子类:每个异常子类可以抛出一个异常。这似乎是一个很好的想法,但我认为为每个可能的错误创建一个子类太过分了。

这就是你说的原因:你的用户可以准确地捕捉到他们想要抓住的东西。

简而言之,就像使用它们一样使用异常。

  

2。创建一个std::exception的子类并抛出它。添加某种错误代码(可能是枚举?还是宏?),以便用户可以检查出错的地方。

您可能希望引入此方法的 little ,其中一个语义类型的异常具有“子类别”或您永远不想过滤的其他辅助数据,但可能属于您的用户的次要兴趣。

答案 1 :(得分:2)

由于程序编写错误而导致的异常应来自std::logic_error。这方面的例子是超出界限的指数。 std::logic_error是您不希望发生的错误,大多数程序都无法从中恢复。

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

异常的类型应该解释出错的地方。开发人员可能感兴趣的任何辅助信息都可以使用what()字符串。如果您想向开发人员提供一系列因果关系,请考虑使用std::throw_with_nested。这允许感兴趣的开发人员在不必查看源代码的情况下从法医上发现他的操作失败的原因。

考虑使用异常层次结构。这允许您的班级的消费者轻松地编码一般的失败情况,同时允许他在个人失败时进行测试。

一个人为的例子:

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