Сегодня, в моем C++ многоплатформенный код, у меня есть выгода попытки вокруг каждой функции. В каждом блоке выгоды я добавляю имя текущей функции к исключению и бросаю его снова, так, чтобы в upmost поймали блок (где я наконец печатаю детали исключения), у меня есть полный стек вызовов, который помогает мне проследить причину исключения.
Действительно ли это - хорошая практика или является там лучшими способами получить стек вызовов для исключения?
Нет, это глубоко ужасно, и я не понимаю, зачем вам нужен стек вызовов в самом исключении - мне вполне достаточно причины исключения, номера строки и имени файла кода, где произошло первоначальное исключение.
Однако, если вам действительно необходимо иметь трассировку стека, то лучше всего генерировать информацию о стеке вызовов ОДИН раз в месте выброса исключения. Не существует единого переносимого способа сделать это, но использование чего-то вроде http://stacktrace.sourceforge.net/ в сочетании с аналогичной библиотекой для VC++ не должно быть слишком сложным.
То, что вы делаете, не является хорошей практикой. И вот почему:
1. В этом нет необходимости.
При компиляции проекта в режиме отладки таким образом, чтобы создавалась отладочная информация, можно легко получить обратные трассировки для обработки исключений в отладчике, таком как GDB.
2. Это громоздко.
Это то, что вы должны помнить, чтобы добавить к каждой функции. Если вы пропустите функцию, это может вызвать большую путаницу, особенно если это была функция, которая вызвала исключение. И любой, кто смотрит на ваш код, должен будет понять, что вы делаете. Кроме того, держу пари, что вы использовали что-то вроде _FUNC_ или _FUNCTION_ или _PRETTY_FUNCTION_, которые, к сожалению, все они нестандартны (в C++ нет стандартного способа получить имя функции).
3. Это медленно.
Распространение исключений в C++ уже довольно медленное, и добавление этой логики только сделает путь кода медленнее. Это не проблема, если вы используете макросы для перехвата и повторного броуза, где вы можете легко игладеть улов и повторный бросок в выпускающих версиях вашего кода. В противном случае производительность может стать проблемой.
Хорошая практика
Хотя перехват и переназначить каждую функцию для создания трассировки стека может быть не очень хорошей практикой, рекомендуется прикреплять имя файла, номер строки и имя функции, при которой изначально было выброшено исключение. Если вы используете boost::exception с BOOST_THROW_EXCEPTION, вы получите это поведение бесплатно. Также полезно прикрепить к вашему исключению пояснительную информацию, которая поможет в отладке и обработке исключения. Тем не менее, все это должно происходить в момент создания исключения; как только он будет построен, ему должно быть позволено распространиться на его обработчика...вы не должны многократно ловить и перестрещать больше, чем это необходимо. Если вам нужно поймать и перенанять в определенной функции, чтобы прикрепить какую-то важную информацию, это нормально, но перехват всех исключений в каждой функции и для целей прикрепления уже доступной информации - это слишком много.
Одно из решений, которое может быть более изящным, заключается в создании макроса/класса Tracer. То есть в верхней части каждой функции вы пишете что-то вроде:
TRACE()
и макрос выглядит примерно так:
Tracer t(__FUNCTION__);
и класс Tracer добавляет имя функции в глобальный стек при построении, и удаляет себя при уничтожении. Тогда этот стек всегда доступен для протоколирования или отладки, обслуживание намного проще (одна строка), и он не несет накладных расходов на исключения.
Примеры реализации включают такие вещи, как http://www.drdobbs.com/184405270, http://www.codeproject.com/KB/cpp/cmtrace.aspx и http://www.codeguru.com/cpp/v-s/debug/tracing/article.php/c4429. Также функции Linux, такие как http://www.linuxjournal.com/article/6391, могут делать это более нативно, как описано в этом вопросе Stack Overflow: How to generate a stacktrace when my gcc C++ app crashes. Также стоит обратить внимание на ACE_Stack_Trace от ACE.
Независимо от этого, метод обработки исключений является грубым, негибким и вычислительно дорогим. Конструирование классов/макрорешения намного быстрее и при желании могут быть скомпилированы для релизных сборок.
Ответ на все ваши проблемы - хороший отладчик, обычно http://www.gnu.org/software/gdb/ в Linux или Visual Studio в Windows. Они могут предоставить вам трассировку стека по запросу в любой момент программы.
Ваш текущий метод - настоящая головная боль производительности и обслуживания. Отладчики созданы для достижения вашей цели, но без накладных расходов.
Необработанное исключение остается для обработки вызывающей функцией. Это продолжается до тех пор, пока исключение не будет обработано. Это происходит с вызовом функции или без него. Другими словами, если вызывается функция, которая не находится в блоке try, исключение, которое происходит в этой функции, автоматически передается в стек вызовов. Итак, все, что вам нужно сделать, это поместить самую верхнюю функцию в блок try и обработать исключение «...» в блоке catch. Это исключение перехватит все исключения. Итак, ваша самая верхняя функция будет выглядеть примерно как
int main()
{
try
{
top_most_func()
}
catch(...)
{
// handle all exceptions here
}
}
. Если вы хотите иметь определенные блоки кода для определенных исключений, вы тоже можете это сделать. Просто убедитесь, что это происходит до блока исключения исключения "...".
Посмотрите на этот вопрос SO . Это может быть близко к тому, что вы ищете. Это не кроссплатформенность, но ответ дает решения для gcc и Visual Studio.