Альтернатива исключению C ++ - PullRequest
13 голосов
/ 01 июня 2010

Я пишу реактивное программное обеспечение, которое многократно получает ввод, обрабатывает его и выдает соответствующий вывод. Основной цикл выглядит примерно так:

initialize();
while (true) {
    Message msg,out;
    recieve(msg);
    process(msg,out);
    //no global state is saved between loop iterations!
    send(out);
}

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

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

Socket s = GlobalResourceHandler.manageSocket(new Socket());
...
try {
    process(msg,out);
catch (...) {
    GlobalResourceHandler.cleanUp();
}

Однако использование исключений запрещено в нашем стандарте кодирования (также в стандарте Google C ++ BTW), в результате весь код компилируется с отключенными исключениями, и я полагаю, что никто не собирается менять то, как все работа только для моей проблемы дизайна.

Кроме того, это код для встраиваемой платформы, поэтому чем меньше мы используем дополнительной функции C ++, тем быстрее становится код и тем более он переносим.

Есть ли альтернативный дизайн, который я могу рассмотреть?

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

Я все еще ищу технический ответ.

Ответы [ 9 ]

7 голосов
/ 01 июня 2010

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

enum ReturnCode
{
  OK = 0,  // OK (there is a reason for it to be 0)
  KO,      // An error occurred, wait for next message
  FATAL    // A critical error occurred, reboot
};

Я должен сказать, FATAL ... исключительный. В приложении нет пути к коду, который его возвращает, кроме инициализации (если вы не инициализированы должным образом, ничего не сможете сделать).

C ++ здесь приносит много с RAII, так как он смеется над несколькими путями возврата и гарантирует детерминированное освобождение объектов, которые он содержит.

Для проверки кода вы можете просто использовать несколько макросов:

// Here is the reason for OK being 0 and KO and Fatal being something else

#define CHECK_RETURN(Expr) if (ReturnCode code = (Expr)) return code;

#define CHECK_BREAK(Expr) if (ReturnCode code = (Expr)) \
    if (KO == code) break; else return code;

Тогда вы можете использовать их так:

CHECK_RETURN( initialize() )
while(true)
{
  Message msg,out;
  CHECK_BREAK( receive(msg) )
  CHECK_BREAK( process(msg,out) )
  CHECK_BREAK( send(out) )
}

Как уже отмечалось, настоящий облом - это конструкторы. Вы не можете иметь «нормальных» конструкторов в такой ситуации.

Возможно, вы можете использовать boost::optional, если вы не можете, я бы действительно советовал дублировать функциональность. Объедините это с системными фабричными функциями вместо конструкторов, и все готово:

boost::optional<MyObject> obj = MyObject::Build(1, 2, 3);
if (!obj) return KO;

obj->foo();

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

6 голосов
/ 01 июня 2010

Если вы не можете throw исключение, тогда альтернативой является return (или return false или подобный код ошибки).

Независимо от того, бросаете вы или возвращаете, вы все еще используете детерминированные деструкторы C ++ для освобождения ресурсов.

Единственное, из чего вы не можете просто «вернуться», это конструктор. Если у вас есть неисправимая ошибка в конструкторе, тогда самое время выбросить; но если вам не разрешено бросать, вместо этого вы должны вернуться, и в этом случае вам понадобится другой способ сообщить о неудаче строительства:

  • Иметь частные конструкторы и статические фабричные методы; иметь фабричный метод, возвращающий нуль в случае неудачи конструкции; не забудьте проверить нулевое возвращение при вызове фабричного метода
  • Имейте свойство "get_isConstructedOk ()", которое вы вызываете после каждого конструктора (и не забывайте вызывать / проверять его для каждого вновь создаваемого объекта)
  • Реализация «двухэтапной» конструкции: в которой вы говорите, что любой код, который может дать сбой, не должен быть в конструкторе, а должен быть в отдельном bool initialize() методе, который вызывается после конструктора забудьте позвонить initialize и не забудьте проверить его возвращаемое значение).
4 голосов
/ 01 июня 2010

Однако использование исключений запрещено в нашем стандарте кодирования (также в Кстати, Google C ++ (кстати). Есть альтернативный дизайн, который я могу рассмотреть?

Краткий ответ: нет .

Длинный ответ да :). Вы можете заставить все функции возвращать код ошибки (аналогично реализации COM-платформы Microsoft.

Основными недостатками этого подхода являются:

  • Вы должны обрабатывать все исключительные случаи явно

  • размер вашего кода резко увеличивается

  • код становится все труднее читать.

Вместо:

initialize();
while (true) {
    Message msg,out;
    recieve(msg);
    process(msg,out);
    //no global state is saved between loop iterations!
    send(out);
}

у вас есть:

if( !succeedded( initialize() ) )
    return SOME_ERROR;

while (true) {
    Message msg,out;
    if( !succeeded( RetVal rv = recieve(msg) ) )
    {
         SomeErrorHandler(rv);
         break;
    }
    if( !succeeded( RetVal rv = process(msg,out) ) )
    {
         SomeErrorHandler(rv);
         break;
    }
    //no global state is saved between loop iterations!
    if( !succeeded( RetVal rv = send(out) ) )
    {
         SomeErrorHandler(rv);
         break;
    }
}

кроме того, реализация всех ваших функций должна будет делать то же самое: окружить каждый вызов функции if.

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

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

Ваш код преобразует каждую функцию из двух строк в восемь строк и так далее. Вы можете улучшить это с помощью дополнительных функций и макросов #define d, но у макросов есть свои особенности, с которыми вам нужно быть очень осторожными.

Короче говоря, ваши стандарты кодирования делают ваш код излишне длинным, более подверженным ошибкам, более сложным для понимания и более сложным в обслуживании.

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

Редактировать : Вы также можете реализовать это с помощью сигналов, но они являются плохой заменой исключений: они делают то же самое, что и исключения, только они также полностью отключают RAII и делают ваш код еще менее элегантным и более подвержен ошибкам.

4 голосов
/ 01 июня 2010

Однако использование исключений запрещено в нашем стандарте кодирования (также в стандарте Google C ++ BTW). Есть ли альтернативный дизайн, который я могу рассмотреть?

Стандарты кодирования, подобные этому, являются орехами.

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

4 голосов
/ 01 июня 2010

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

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

0 голосов
/ 02 июня 2010

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

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

Например

#define WHILE_R(cond,return_value) while (cond) {\
    if (exception_thrown) return return_value
#define ENDWHILE() }

bool isPolyLegal(Poly p) {
    PolyIter it(p);
    WHILE_R(it.next(),true) //return value is arbitary
    ...
        if (not_enough_memory) exception_thrown = true;
    ...
    ENDWHILE()
}
0 голосов
/ 01 июня 2010

Вы называете какие-нибудь библиотеки, которые могут вызывать исключения? Если это так, вам все равно понадобится попытка поймать. Для ваших внутренних ошибок каждый метод должен будет возвращать код ошибки (используйте return только для кода ошибки, используйте ссылочные параметры для возврата фактических значений). Если вы хотите, чтобы очистка памяти была надежной на 100%, вы можете запустить свое приложение, используя приложение монитора. Если ваше приложение падает, монитор запускает его снова. Вам все еще нужно закрыть файлы и соединение с БД, хотя.

0 голосов
/ 01 июня 2010

От макушки головы вы можете достичь чего-то похожего с сигналами.

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

Может быть что-то большее, чем это, так какЯ не обдумал это.

Надеюсь, это поможет.

0 голосов
/ 01 июня 2010

Если вы работаете под Windows, вы можете использовать исключения SEH. У них также есть преимущество обработчика предварительного стэка-размотки, который может остановить размотку (EXCEPTION_CONTINUE_EXECUTION)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...