2015-04-28 2 views
8

Я собираюсь начать этот новый проект на C++, и я думаю о том, что это нелогичный способ обработки ошибок. Теперь я не собираюсь начинать бросать и ловить исключения, и, вполне возможно, никогда не брошу исключений вообще, но я думал - даже для регулярной обработки ошибок, зачем откатывать свой собственный/copy-paste класс для описания ошибок/статус, когда я мог бы просто использовать std::exception и его дочерние классы (или, возможно, std::optional<std::exception>)?Могу ли я/Должен ли я использовать std :: exception для регулярной обработки ошибок?

using Status = std::optional<std::exception>; 
Status somethingThatMayFail(int x); 

Есть ли кто-нибудь или любой проект, работающий таким образом? Это смешная идея или немного скрипучие?

+0

@vsoftco: См. Редактирование. Я говорю об обработке ошибок исключения исключений. – einpoklum

+0

Интересно ... Недавно я слышал, что Google, похоже, использует такую ​​систему, не уверен, правда или нет. Что делать, если ваши функции должны что-то вернуть? Будете ли вы использовать пару/кортеж? – vsoftco

+0

@vsoftco: Давайте не будем вдаваться в подробности ... вопрос в том, что использовать для сообщения об ошибках. Но - либо функция будет иметь параметр ввода/вывода (ссылка или указатель), либо, может быть, она вернет некоторый класс case (я уверен, что на C++ сейчас что-то вроде этого лучше, чем C-союзы). – einpoklum

ответ

2

Я считаю, что производительность сама по себе может оказаться проблематичной. Рассмотрим следующий код:

#include <iostream> 
#include <chrono> 
#include <ctime> 
#include <stdexcept> 

#include <boost/optional.hpp> 


int return_code_foo(int i) 
{ 
    if(i < 10) 
     return -1; 
    return 0; 
} 


std::logic_error return_exception_foo(int i) 
{ 
    if(i < 10) 
     return std::logic_error("error"); 
    return std::logic_error(""); 
} 


boost::optional<std::logic_error> return_optional_foo(int i) 
{ 
    if(i < 10) 
     return boost::optional<std::logic_error>(std::logic_error("error")); 
    return boost::optional<std::logic_error>(); 
} 


void exception_foo(int i) 
{ 
    if(i < 10) 
     throw std::logic_error("error"); 
} 


int main() 
{ 
    std::chrono::time_point<std::chrono::system_clock> start, end; 

    start = std::chrono::system_clock::now(); 
    for(size_t i = 11; i < 9999999; ++i) 
     return_code_foo(i); 
    end = std::chrono::system_clock::now(); 
    std::cout << "code elapsed time: " << (end - start).count() << "s\n"; 

    start = std::chrono::system_clock::now(); 
    for(size_t i = 11; i < 9999999; ++i) 
     return_exception_foo(i); 
    end = std::chrono::system_clock::now(); 
    std::cout << "exception elapsed time: " << (end - start).count() << "s\n"; 

    start = std::chrono::system_clock::now(); 
    for(size_t i = 11; i < 9999999; ++i) 
     return_optional_foo(i); 
    end = std::chrono::system_clock::now(); 
    std::cout << "optional elapsed time: " << (end - start).count() << "s\n"; 

    start = std::chrono::system_clock::now(); 
    for(size_t i = 11; i < 9999999; ++i) 
     exception_foo(i); 
    end = std::chrono::system_clock::now(); 
    std::cout << "exception elapsed time: " << (end - start).count() << "s\n"; 

    return 0; 
} 

на моем CentOS, используя GCC 4.7, он приурочен на:

[[email protected] tmp]$ ./a.out 
code elapsed time: 39893s 
exception elapsed time: 466762s 
optional elapsed time: 215282s 
exception elapsed time: 38436s 

в настройках ванили, а также:

[[email protected] tmp]$ ./a.out 
code elapsed time: 0s 
exception elapsed time: 238985s 
optional elapsed time: 33595s 
exception elapsed time: 24350 

в -O2 настройках.

P.S. Я лично использовал бы исключения/стекирование из-за убеждения, что это фундаментальная часть C +, возможно, как @vsoftco сказал выше.

+0

Ну, да, точка взята, но вы измеряли время, когда на самом деле происходит ошибка, а не когда нет. Кроме того, если мой код обработки ошибок, используя числовой код состояния, должен был передать строку где-нибудь или записать ее в поток, что бы приравнять время работы, которое я бы подумал. – einpoklum

+0

@einpoklum На самом деле, я не согласен - если вы проверите код, «исключение» никогда не происходило в этом тесте. В этом и заключается проблема с этим подходом - для обычного случая это кажется чрезвычайно дорогим. Это также относится к вашей точке зрения о фактическом обращении с ошибками - это, как правило, намного реже. (Если вы думаете об этом, дахпомашеху :-)) –

3

Я не думаю, что вы должны создавать исключения, если вы на самом деле не собираетесь их бросить. Я бы рекомендовал тип возврата bool или enum. Цель будет намного понятнее, кто-то читает ваш код, и они будут быстрее. Однако, если вы создадите исключение, кто-то еще придет и подумает, что они могут выбросить исключение и привести к сбою всей системы.

Исключения C++ играют важную роль в управлении ресурсами, вызывая деструкторы и все это (RAII). Использование их каким-либо другим способом может повредить работе и (что еще более важно) сбить с толку святого из любого, кто пытается сохранить код, позже.

Вы можете, однако, сделать то, что хотите, с классом отчетности о состоянии, который НЕ имеет ничего общего с std :: exception. Люди делают слишком много для «быстрого» кода, когда им это не нужно. Если перечисление состояния недостаточно, и вам нужно вернуть больше информации, тогда будет работать класс отчетности о состоянии. Если это упрощает чтение кода, тогда идите.

Просто не называйте это исключением, если вы его не выбросите.

+0

Это. Даже если 'optional ' идеально подходит для использования, использование его вне контекста throw/catch будет путать. По возможности следует избегать путаницы. –

0

Чтобы ответить на ваш вопрос, это не смехотворная идея, и вы можете использовать std::exception для регулярной обработки ошибок; с некоторыми оговорками.

Использование std::exception в качестве результата функции

Допустим, что функция может выйти в несколько ошибочных состояний:

std::exception f(int i) 
{ 
    if (i > 10) 
    return std::out_of_range("Index is out of range"); 

    if (can_do_f()) 
    return unexpected_operation("Can't do f in the current state"); 

    return do_the_job(); 
} 

Как вы можете справиться с этим с std::exception или опциональным? Когда функция вернется, будет создана копия исключения, содержащая только часть std::exception и отклоняющую специфику фактической ошибки; оставив вам единственную информацию, которая «да, что-то пошло не так ...». Поведение будет таким же, как возврат логического или необязательного ожидаемого типа результата, если таковой имеется.

Использование std::exception_ptr сохранить специфику

Другим решением было бы применить тот же подход, чем в std::promise, то есть вернуть std::exception_ptr. Там вы сможете возвращать либо ничего, либо исключение, сохраняя при этом фактические данные об ошибках. Восстановление фактического типа ошибки может быть сложным.

Возвратившись ошибку или результат в том же самом объекте

Наконец другой вариант будет использовать Expected<T>proposal и its implementation. Там вы сможете вернуть значение или ошибку в одном объекте и обработать эту ошибку, если хотите (путем проверки ошибки или с регулярной обработкой исключений), с некоторыми особенностями для функции, не возвращающей значения (подробнее на Stack Overflow или по телефону this blog).

Как выбрать

Мое личное мнение по этому вопросу, что, если вы собираетесь использовать исключения, а затем использовать их так, как они были разработаны, в конечном итоге с некоторыми дополнительными инструментами, как Expected<T>, чтобы облегчить , В противном случае, если вы не можете использовать стандартную обработку исключений, тогда перейдите к решению, которое зарекомендовало себя, как классическая система кодов ошибок.

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