Я собираюсь начать этот новый проект на C ++ и думаю о безболезненном способе обработки ошибок. Теперь я не собираюсь начинать выбрасывать и перехватывать исключения, и, возможно, вообще никогда не буду генерировать исключения, но я думал — даже для регулярной обработки ошибок, зачем мне свой собственный / copy-paste класс для описания ошибок / статус, когда я мог просто использовать std::exception
и его дочерние классы (или, возможно, std::optional<std::exception>
)?
using Status = std::optional<std::exception>;
Status somethingThatMayFail(int x);
Кто-нибудь / любой проект работает таким образом? Это смешная идея или немного скрипучая?
Я не думаю, что вы должны создавать исключения, если вы на самом деле не собираетесь их создавать. Я бы порекомендовал тип возврата bool или enum. Намерение будет намного понятнее тому, кто читает ваш код, и они будут быстрее. Однако, если вы создадите исключение, придет кто-то другой и подумает, что он может выбросить исключение и вызвать крах всей системы.
Исключения C ++ играют важную роль в управлении ресурсами, вызывая деструкторы и все такое (RAII). Использование их любым другим способом приведет к снижению производительности и (что еще более важно) приведет к путанице в каждом, кто попытается сохранить код позже.
Однако вы можете делать то, что хотите, с классом отчетов о состоянии, который НЕ имеет никакого отношения к std :: exception. Люди делают слишком много для «более быстрого» кода, когда им это не нужно. Если перечисление статуса недостаточно хорошее, и вам нужно вернуть больше информации, тогда будет работать класс отчетов о состоянии. Если это делает код более легким для чтения, тогда сделайте это.
Только не называйте это исключением, если вы на самом деле не бросаете это.
Я думаю, что одно только выполнение может оказаться проблематичным. Рассмотрим следующий код:
#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, он рассчитан на:
[amit@amit tmp]$ ./a.out
code elapsed time: 39893s
exception elapsed time: 466762s
optional elapsed time: 215282s
exception elapsed time: 38436s
в настройках ванили, и:
[amit@amit tmp]$ ./a.out
code elapsed time: 0s
exception elapsed time: 238985s
optional elapsed time: 33595s
exception elapsed time: 24350
в настройках -O2.
Постскриптум Лично я бы использовал исключения / разматывание стека из-за убеждения, что это фундаментальная часть C +, возможно, как сказал @vsoftco выше.
Чтобы ответить на ваш вопрос, это не смешная идея, и вы можете использовать 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::exception_ptr
, Там вы сможете вернуть либо ничего, либо исключение, сохраняя при этом фактическую информацию об ошибке. Восстановление фактического типа ошибки может быть сложным.
Возврат ошибки или результата в том же объекте
Наконец, другой вариант будет использовать Expected<T>
предложение, а также его реализация. Там вы сможете вернуть значение или ошибку в одном объекте и обработать ошибку по вашему желанию (путем проверки ошибки или с регулярной обработкой исключений), с некоторыми особенностями в случае функции, не возвращающей значение (подробнее на Переполнение стека или на этот блог).
Как выбрать
Мое личное мнение по этому вопросу состоит в том, что если вы собираетесь использовать исключения, то используйте их так, как они были спроектированы, в конечном итоге с некоторыми дополнительными инструментами, такими как Expected<T>
чтобы было проще. В противном случае, если вы не можете использовать стандартную обработку исключений, тогда найдите решение, которое зарекомендовало себя, как классическая система кодов ошибок.