Существует множество мнений по поводу механизма исключений: некоторые называют исключения "скрытым goto", другие же считают исключения отличным механизмом и предлагают его использовать везде и всегда. Единственное, с чем согласны все - исключения нужно использовать с особой осторожностью. Ведь даже с виду совсем безобидный код может привести к тому, что ваше приложение упадёт в самый неподходящий момент.
С++, как многим известно, предоставляет некий механизм спецификации исключений - exceptions spectifications(п.15.4), простейший пример которого представлен ниже:
void foo() throw(Exception1, Exception2)
{
//...
}
В этом небольшом примере функцию
foo описывают как функцию, которая может бросить исключения
Exception1 и
Exception2. Однако, как пишут в
Boost Requirements and Guidelines:
The biggest problem with exception-specifications is that programmers use them as though they have the effect the programmer would like, instead of the effect they actually have.
или
Основная проблема со спецификацией исключений в том, что программисты используют их, как будто они работают так, как этого хочет программист, а не так, как они действуют на самом деле
Действительно, попробуйте в функции
foo бросить исключение
Exception3 и скомпилировать при помощи GCC(другие компиляторы я не использую, но думаю, что они действуют также). Ничего не произошло? И у меня то же самое :)
Т.е., спецификация исключений - вещь относительно бесполезная, а иногда и вредная(некоторые компиляторы не инлайнят функции с exception specifications). Существует много тредов-обсуждений в листе рассылки GCC по поводу возможности вывода предупреждений о несоответствующем спецификации использовании исключений(вот
это сообщение - самое полезное, по-моему). В конце концов, подобные запросы отклоняются, ибо "невозможно реализовать из-за сложности межпроцедурного анализа. GCC не предназначен для такого".
Однако, это не остановило проект
EDoc++, о котором и хотелось бы рассказать подробней. Он использует пропатченный GCC для анализа кода на ошибки при использовании исключений. Например, у нас есть код:
#include <iostream>
class C1{};
class C2{};
void foo() throw (C1)
{
throw C2();
};
class C
{
public:
~C()
{
foo();
}
};
int main()
{
C c;
}
В этом примере есть несколько ошибок, связанных с исключениями:
- Не соблюдается спецификация в функции foo
- Бросается исключение из деструктора
- Исключение выходит за функцию main, что приводит к аварийному завершению программы
Попробуем проанализировать наш код с использованием EDoc++. Сначала EDoc++ надо
собрать, потом
проинициализировать окружение. В итоге мы получаем bash-оболочку с приглашением "EDOC ->". Я сохранил вышеприведенный С++ файл в
~/dev/sandbox/t.cpp. Компилируем(все дополнительные опции смотрите в документации к EDoc++):
EDOC -> g++ -fedoc-source -c ~/dev/sandbox/t.cpp -o ~/dev/sandbox/t.o
EDoc++ создает дополнительный файлик, который при опции -fedoc-source попадает в ~/dev/sandbox/t.cpp.edc. Показываем полученные результаты:
EDOC -> edoc --show-all --format simple ~/dev/sandbox/t.cpp.edc
F: _GLOBAL__D__Z3foov()
F: _GLOBAL__I__Z3foov()
F: foo()
F: __static_initialization_and_destruction_0(int, int)
F: C::~C()
F: main()
ERROR(ECRASH_SPEC): Exception may propogate through restrictive specifier :The function: foo() can throw an exception of type: C2 that is not allowed by its specifier list: throws(C1)
Кажется, лучше сообщение об ошибке и не укажешь. Исправим ошибку - будем делать
throw C1; вместо
throw C2;. Перекомпилируем и посмотрим:
EDOC -> edoc --show-all --format simple ~/dev/sandbox/t.cpp.edc
[...skip...]
ERROR(EMAIN_EXC): An exception may propogate out the main function. :The exception: C1 may propagate out the main function which may cause a program abort.
WARNING(WDESTR_EXC): An exception may propogate through a destructor. :Destructor: C::~C(), Exception: C1
Ага! Мы чуть-чуть не упустили исключение за пределы деструктора. Это ужасно. Уберём наконец
throw С1; из
foo для того чтобы проверить, как EDoc++ отреагирует на спецификацию без реального выбрасывания исключений:
EDOC -> edoc --show-all --format simple ~/dev/sandbox/t.cpp.edc
[...skip...]
Молчит. Т.е., он не принимает во внимание спецификации, если эти спецификации врут, что, в принципе, правильно.
EDoc++ обладает еще множеством достоинств: он поддерживает множество форматов вывода, множество способов хранения .edc информации, судя по отличной документации он правильно работает с указателями на функции, которые бросают исключения, и умеет находить ошибки даже в тех случаях, когда они делятся между несколькими модулями компиляции. В общем, отличный инструмент, спасибо создателям :)