Могу ли я / должен использовать std :: exception для обычной обработки ошибок?

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

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

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

7

Решение

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

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

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

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

3

Другие решения

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

#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 выше.

2

Чтобы ответить на ваш вопрос, это не смешная идея, и вы можете использовать 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> чтобы было проще. В противном случае, если вы не можете использовать стандартную обработку исключений, тогда найдите решение, которое зарекомендовало себя, как классическая система кодов ошибок.

0
По вопросам рекламы [email protected]