2015-05-19 4 views
3

Предположим, у меня есть библиотека C++. В некоторых случаях библиотека будет исключать исключения. Я хотел бы сделать это возможным и простым для пользователя этой библиотеки, чтобы поймать эти исключения и сказать, что пошло не так. Каков наилучший способ справиться с этим? Я придумал следующие возможные решения:Как реализовать мои исключения на C++?

  1. Просто используйте стандартные классы исключений с настраиваемыми сообщениями об ошибках. Это, однако, раздражало бы того, кто ловил исключение, чтобы сказать, что было не так.
  2. Создайте подкласс std::exception и выбросьте его. Добавьте код ошибки (возможно, enum? Или макрос?), Чтобы пользователь мог проверить, что пошло не так.
  3. Создайте несколько подклассов исключений: по одному для каждого возможного случая может быть выбрано исключение. Это похоже на опрятную идею, но я думаю, что слишком сложно создавать подкласс для каждой возможной ошибки.

Я не могу решить. Каков правильный путь?

+0

Иерархии исключений являются основополагающими для поддерживаемых программ и библиотек C++. Ответы на этот вопрос содержат важную информацию о принятой передовой практике. Вопрос не должен быть приостановлен. –

ответ

2

Исключения, возникающие из-за неправильной записи программы, должны быть получены из std::logic_error. Примерами этого являются индексы, выходящие за пределы. A 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; 
}; 

EDIT: по запросу, полный рабочий пример использования 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 
+0

Спасибо за ваш ответ! Не могли бы вы подробнее рассказать о 'std :: throw_with_nested'? – Venemo

+0

@ Venemo ответ обновляется с помощью компилируемого примера. –

6

3. Создайте несколько подклассов исключений: по одному для каждого возможного случая может быть выбрано исключение. Это похоже на опрятную идею, но я думаю, что слишком сложно создавать подкласс для каждой возможной ошибки.

Это по причине, о которой вы заявили: ваши пользователи могут затем поймать именно то, что они хотят поймать.

Короче говоря, используйте исключения, поскольку они предназначались для использования.

2. Создайте подкласс std::exception и выбросьте его. Добавьте код ошибки (возможно, enum? Или макрос?), Чтобы пользователь мог проверить, что пошло не так.

Вы можете бросить в небольшой этого подхода, в котором один семантический тип исключения имеет «подкатегорию» или другие вспомогательные данные, которые вы никогда не хотите фильтровать, но может быть второстепенным для ваших пользователей.

Смежные вопросы