2011-09-29 5 views
2

Я понимаю преимущества использования обработки исключений на C++, и я знаю, что это может быть сложно. Одно из правил гласит, что каждая функция может бросить. Хорошо, но есть ситуации, когда мы хотим убедиться, что эта функция не выбрасывает. Я ищу любую известную практику или рекомендации по работе с такими ситуациями. Примеры:Рекомендации по обработке исключений для C++

try 
{ 
    // do something 
} 
catch (std::runtime_error& error) 
{ 
    save_log (error); 
    emit_dbus_signal (error); 
} 

Я не волнует, если save_log() или emit_dbus_signal() не получится, я только хочу, чтобы убедиться, что я пытался назвать их.

ThreadPool thread_pool; 
SocketsPool socket_pool; 
MainLoop main_loop; 

try 
{ 
    thread_pool.init(); 
    socket_pool.init(); 

    main_loop.run(); 
} 
catch (std::runtime_error& error) 
{ 
    save_log (error); 
    emit_dbus_signal (error); 
} 

thread_pool.finalize(); 
socket_pool.finalize(); 

Я только хочу, чтобы убедиться, что я пытался завершить thread_pool и socket_pool. Любая ошибка во время процесса финализации должна обрабатываться внутри методов finalize().

Я помню, какие функции не выбрасывают, но он будет работать только для небольших программ. Должен ли я добавлять суффикс, например _nothrow, к таким именам функций «не метания» и обрабатывать это при написании кода? Спецификация исключений устарела с C++ 11, поэтому я хочу ее избежать. Как насчет noexcept? Я все еще не уверен, понимаю ли я эту новую функцию. Это то, что я ищу?

Нет контроля над проверкой во время компиляции в C++ 11 справа?

Или, может быть, я полностью отжимаю? :)

+1

Предложение книги: «Исключительный C++» от Herb Sutter –

ответ

3

Думаю, что вы должны понимать RAII как отправную точку.

Как только вы используете RAII правильно, вы обнаружите, что большинство случаев, когда вы считали, что вам нужно вручную завершать объекты, волшебным образом исчезли: И во многих случаях это означает, что вы можете обойтись без полностью использовать метод try/catch/finalize.

Как уже говорили другие, вы все еще будете хотеть ваши save_log/emit_dbus_signal звонки в водосборный заявление где-то ...

В приведенном выше случае, конструктор Threadpool был бы называть Init() и деструктор назвали бы завершить().

+6

's/catch/finally /'. 'catch' не уходит с RAII; потребность в гипотетическом блоке 'finally'. –

+0

Что делать, если мне нужно завершить объекты в определенном порядке? – Goofy

+1

Объекты всегда разрушаются в обратном порядке до порядка, в котором они были построены. – Alan

0

Для финализации функций, вам следует использовать классы-оболочки с принципом RAII вместо, то есть переписать код с помощью:

try { 
    initialized_thread_pool pool = thread_pool.init(); 
} catch (std::runtime_error& error) { handle(error); } 

и сделать доработку в деструктор initialized_thread_pool.

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

0

Читайте на RAII http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization

Если функция бросает любые стек переменных в этой функции все равно будет иметь их деструкторы называется. Это означает, что вы можете просто вызвать finalize() внутри деструкторов ~ThreadPool() и ~SocketPool().

Если вы не можете изменить классы ThreadPool или SocketPool, вы можете написать свои собственные классы-оболочки, которые вызывают завершение при уничтожении, например.

class ScopedThreadPool 
{ 
public: 
    ScopedThreadPool(ThreadPool &threadPool) : m_threadPool(threadPool) {} 
    ~ScopedThreadPool() { m_threadPool.finalize(); } 

private: 
    ThreadPool &m_threadPool; 
}; 

И затем использовать его как это ...

ThreadPool threadPool; 
ScopedThreadPool scopedThreadPool(threadPool); 

При выходе из функции (либо через return или throw), то ScopedThreadPool деструктор будет вызывать finalize() для вас.

+1

Почему downvote? – Alan

3

Вы должны обязательно документировать, какие функции гарантированно не бросать, не просто «помните»!

Общим примером является то, что функции swap не должны быть выбраны. В этом случае нет оснований ставить nothrow на имя, это довольно фундаментально, что многие виды использования swap должны быть некратными. Вы также можете сделать правило для своего проекта, чтобы функции log никогда не бросали. Но это все еще нужно документировать. В идеале каждая функция должна документировать то, что она выбрасывает и почему, но при этом не может гарантировать, что каждая функция должна обязательно документировать, какой уровень гарантии исключения он предлагает, а «nothrow» - самый сильный уровень.

Если бы у меня были метательные и не метательные версии той же функциональности, то лично я бы поставил nothrow на имя, чтобы отличить их. Помимо этого, посмотрите, как выглядит код. Возможно, вы обнаружите, что пишете фрагмент кода, в котором вы вызываете семь функций подряд, все из которых должны быть нечеткими, чтобы код был правильным. Вероятно, было бы полезно, чтобы будущим читателям не приходилось идти и проверять декларацию каждой из этих функций, чтобы убедиться, что она действительно не бросает, хотя IDE помогают в этом. Конечно, они не хотят читать 7 файлов документов, если этого избежать. В этом случае, я полагаю, было бы полезно иметь бородавки в венгерском стиле в именах функций, но такая вещь может быстро выйти из-под контроля и сделать код более трудным для чтения, а не проще.

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

Пустые исключения указаны в порядке, а C++ 11 noexcept, вероятно, лучше. Помимо их значения компилятору, они помогают с документацией. Это непустые спецификации исключений, которые являются неприятными.

Я согласен с тем, что все говорят о finalize: для этого нужны деструкторы.

1

Это зависит от того, что вы на самом деле собираетесь делать. Когда вы говорите, что вам все равно, save_log или emit_dbus_signal терпит неудачу, вы не говорите, что все равно означает. То есть, если save_log терпит неудачу, вы хотите еще попробовать и emit_dbus_signal? Если да, то вы можете:

catch (std::runtime_error& error) { 
    try { save_log(error); } catch (...) {} 
    try { emit_dbus_signal(error); } catcn (...) {} 
} 

Если вы не заботитесь о emit_dbus_signal не вызываются, если save_log выходит из строя, другой подход был бы ограждающим всем try/catch внутри второго try/catch:

try { 
    try { 
    // current code 
    } catch (std::runtime_error const & error) { 
    // current handling 
    } 
} catch (...) {} // ensure that no other exception escapes either the try or the catch blocks 
thread_pool.finalize(); 
socket_pool.finalize(); 

Есть на самом деле другие подходы, например, использование RAII для обеспечения вызова threadpool.finalize() вне зависимости от того, как функция завершается в строках ScopeGuard.

0

Добавить throw() в конец объявления функции.Компилятор (по крайней мере, gcc) будет жаловаться, если функция генерирует исключения.

void function() throw(); 

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

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