Подтвердить что ты не робот

Как создавать исключения "типы" в С++?

Два анти-шаблона, которые невероятно распространены в большинстве базовых кодов, которые я разработал, - это логические значения возврата, указывающие на успех/неудачу, и общие интегральные коды возврата, чтобы указать более подробную информацию об ошибке.

Оба они очень похожи на C и не очень хорошо вписываются в С++ по моему скромному мнению.

Мой вопрос касается лучших практик, когда дело доходит до разработки исключений в вашей базе кода. Другими словами, какой лучший способ указать конечные возможности неудачи? Например, один из вышеупомянутых анти-шаблонов обычно имеет одну гигантскую перечисление с каждым значением перечисления, представляющим определенный вид сбоя, такой как FILE_DOES_NOT_EXIST или NO_PERMISSIONS. Обычно они поддерживаются как можно более общие, поэтому их можно использовать в нескольких несвязанных доменах (таких как сетевые компоненты и файлы IO-компонентов).

Дизайн, подобный этому, который можно было бы рассмотреть для исключений, заключается в подклассе одного конкретного типа исключения из std::exception для каждого типа сбоя или вещи, которая может пойти не так. Поэтому в моем предыдущем примере у нас было бы следующее:

namespace exceptions {
  class file_does_not_exist : public std::exception {};
  class no_permissions : public std::exception {};
}

Я думаю, что это ближе к чему-то, что "чувствует себя лучше", но, в конце концов, это похоже на кошмар для обслуживания, особенно если у вас есть сотни этих "кодов ошибок" для перевода на классы.

Другой подход, который я видел, - просто использовать стандартные классы <stdexcept>, такие как std::runtime_error, и иметь строку со спецификой. Например:

throw std::runtime_error( "file does not exist" );
throw std::runtime_error( "no permissions" );

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

Итак, что было бы хорошим, поддерживаемым дизайном для типов исключений? Мои требования просты. Я хотел бы иметь контекстуальную информацию о том, что произошло (у меня закончилась нехватка памяти? Мне не хватает прав на файловую систему? Не удалось ли я выполнить предварительные условия вызова функции (например, плохие параметры)?), И мне также хотелось бы, чтобы иметь возможность действовать в соответствии с этой информацией. Возможно, я отношусь ко всем тем же, возможно, у меня есть определенные инструкции catch для определенных сбоев, поэтому я могу восстановить их по-другому.

Мои исследования по этому вопросу привели меня только к этому вопросу: дизайн класса исключений С++

Здесь пользователь задает аналогичный вопрос, который я есть, и его образец кода внизу очень симпатичен, но его базовый класс исключений не следует принципу открытого/закрытого, поэтому это не будет работать для меня.

4b9b3361

Ответ 1

Стандартная библиотека С++. Иерархия исключений ИМХО довольно произвольна и бессмысленна. Например, это, вероятно, просто создаст проблемы, если кто-нибудь начнет фактически использовать, например. std::logic_error вместо прекращения, когда это ясно, что программа имеет очень неприятную ошибку и торговлю;. Поскольку стандарт ставит его,

"Отличительной особенностью логических ошибок является то, что они связаны с ошибками во внутренней логике программы".

Таким образом, в тот момент, когда в противном случае было бы разумно выбросить std::logic_error, состояние программы может быть непредсказуемо затушевано, и продолжение выполнения может привести к тому, что данные пользователя будут повреждены.

Тем не менее, как и std::string, стандартная иерархия классов исключений имеет действительно очень важную и полезную функцию, а именно, что она формально стандартная.

Таким образом, любой настраиваемый класс исключений должен быть получен косвенно или (хотя я бы не рекомендовал его) непосредственно из std::exception.

Как правило, когда дебаты о настраиваемых классах исключений бушевали десять лет назад, я рекомендовал получить только от std::runtime_error, и я по-прежнему рекомендую это. Это стандартный класс исключений, который поддерживает пользовательские сообщения (другие обычно имеют жестко запрограммированные сообщения, которые предпочтительно не следует изменять, поскольку они имеют ценность в распознавании). И можно утверждать, что std::runtime_error является стандартным классом исключений, который представляет собой восстановимые отказы (в отличие от неустранимых логических ошибок, которые могут быть исправлены во время выполнения) или, как полагает стандарт,

"ошибки времени выполнения связаны с событиями, выходящими за рамки программы. Их невозможно легко предсказать заранее.

Иногда механизм исключений С++ используется для других вещей, рассматриваемых как просто динамический механизм перехода на динамический уровень. Например, умный код может использовать исключения для распространения успешного результата из цепочки рекурсивных вызовов. Но исключение-как-неудача является наиболее распространенным использованием, и что, как правило, оптимизируются исключения С++, поэтому в основном имеет смысл использовать std::runtime_error как root для любой пользовательской иерархии классов исключений – даже если это заставляет кого-то, кто хочет быть умным, бросать "неудачу" - указание исключения, указывающего на успех & hellip;

Стоит отметить: существует три стандартных подкласса std::runtime_error, а именно std::range_error, std::overflow_error и std::underflow_error, и что, вопреки тому, что указывают их имена, последние два не должны генерироваться с плавающей запятой операций и на практике не генерируются операциями с плавающей запятой, но AFAIK генерируется только некоторыми; сюрприз! – std::bitset. Проще говоря, стандартная библиотека иерархии классов, как мне кажется, была заброшена там просто для удобства, без каких-либо реальных веских причин или существующей практики, и даже без проверки подлинности. Но, возможно, я пропустил это, и если да, то у меня все еще есть что-то новое, чтобы узнать об этом.: -)

Итак, std::runtime_error это то.

В верхней части иерархии пользовательских классов исключений, с С++ 03, было полезно добавить важный материал, отсутствующий в стандартных исключениях С++ 03:

  • Виртуальный clone метод (особенно важный для передачи исключений через C-код).

  • Виртуальный throwSelf метод (та же основная причина, что и для клонирования).

  • Поддержка цепочки исключений (стандартизация формата).

  • Поддержка переноса кода сбоя (например, код ошибки Windows или Posix).

  • Поддержка получения стандартного сообщения из приведенного кода причины сбоя.

С++ 11 добавила поддержку для большей части этого, но за исключением проверки новой поддержки кодов причин сбоев и сообщений, и отмечая, что, к сожалению, она довольно специфична для Unix и не очень подходит для Windows, я haven & rsquo; t все же использовал его. Во всяком случае, для полноты: вместо добавления клонирования и виртуального ретронирования (что лучше всего, что обычный программист приложений может выполнять в пользовательской иерархии классов исключений, потому что в качестве программиста приложений вы не можете поднять текущий объект исключения из хранилища, что реализация & rsquo; используется использование исключения s), в стандарте С++ 11 добавляются бесплатные функции std::current_exception() и std::rethrow_exception(), а вместо поддержки цепочки исключений исключений добавляется класс mixin std::nested_exception и бесплатные функции std::rethrow_nested и std::rethrow_if_nested.

Учитывая частичную поддержку С++ 11 для указанных выше маркеров, новая и современная иерархия классов исключений должна лучше интегрироваться с поддержкой С++ 11 вместо адресации недостатков С++ 03. Ну, за исключением кода С++ 11, который кажется очень непригодным для программирования Windows. Таким образом, в верхней части пользовательской иерархии, прямо под std::runtime_error, в идеале должен быть хотя бы один общий класс исключений и получен из этого один класс исключений, который поддерживает распространение кодов сбоев.

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

Я говорю "нет": DON & T; T ADD NEEDLESS COMPLEXITY.

Если или где это может быть полезно для вызывающего, чтобы отличить определенную причину сбоя, для этого очень полезен отдельный класс исключений. Но в большинстве случаев единственная информация, представляющая интерес для вызывающего, является единственным фактом, что произошло исключение. Очень редко, что различные причины отказа приводят к различным попыткам исправления.

Но как насчет причин сбоев?

Ну, когда это то, что дает вам базовый API, просто добавлена ​​работа по созданию соответствующих классов исключений. Но, с другой стороны, когда вы сообщаете о сбое в цепочке вызовов, и вызывающему абоненту может понадобиться знать точную причину, тогда использование кода для этого означает, что вызывающему абоненту придется использовать некоторую вложенную проверку и отправку внутри catch. Таким образом, это разные ситуации: (A) ваш код является исходным источником индикации отказа, в отличие от (B) вашего кода, например. функция Windows или Posix API, которая терпит неудачу, и что это указывает на причину сбоя с помощью кода причины сбоя.

Ответ 2

Я использовал boost:: exception какое-то время, и мне очень нравится вставлять произвольные данные в исключение. Я делаю это в дополнение к конкретным типам исключений, например

#define MY_THROW(x) \
    BOOST_THROW_EXCEPTION(x << errinfo_thread_id(boost::this_thread::get_id()))

class DatabaseException : public std::exception, public boost::exception { ... };

typedef boost::error_info< struct errinfo_message_, std::string > errinfo_message;

MY_THROW(DatabaseException(databaseHandle)
         << boost::errinfo_api_function("somefunction")
         << errinfo_message("somefunction failed terribly.")
         );

Таким образом вы можете поймать определенные исключения, одновременно предоставляя множество деталей из сайта throw (например, имя файла, номер строки, идентификатор потока,...).

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

РЕДАКТИРОВАТЬ: Как отмечено в потоке, который вы указали, используйте неглубокие иерархии. Я использую что-то вроде 3-4 классов исключений, которые наследуют непосредственно из std:: exception и boost:: exception. Я также помещал множество подробностей в исключения (например, идентификатор потока).

Ответ 3

Если ваше настраиваемое исключение указывает на ошибку программирования (т.е. программист мог бы предотвратить условие), то получается из logic_error. Эти ошибки не могут быть восстановлены, потому что программист, возможно, не ожидал этого на первом месте. Например, кто-то пытается делить на 0.

class divide_by_zero : public logic_error {
public:
    divide_by_zero(const string& message)
        : logic_error(message) { 
    }
}

Если условие ошибки было чем-то, что не могло быть предотвращено программистом, тогда вывести из runtime_error. Обычно эти ошибки могут быть восстановлены (т.е. Программист может перехватить исключение и повторить попытку или продолжить).

class network_down : public runtime_error {
public:
    network_down(const string& message)
        : runtime_error(message) { 
    }
}

Это также общая философия для разработки исключений в стандартной библиотеке. Вы можете просмотреть код исключения для GCC здесь.