34

Today, in my C++ multi-platform code, I have a try-catch around every function. In every catch block I add the current function's name to the exception and throw it again, so that in the upmost catch block (where I finally print the exception's details) I have the complete call stack, which helps me to trace the exception's cause.

Is it a good practice, or are there better ways to get the call stack for the exception?

Igor
  • 26,650
  • 27
  • 89
  • 114

10 Answers10

21

What you are doing is not good practice. Here's why:

1. It's unnecessary.
If you compile your project in debug mode so that debugging information gets generated, you can easily get backtraces for exception handling in a debugger such as GDB.

2. It's cumbersome.
This is something you have to remember to add to each and every function. If you happen to miss a function, that could cause a great deal of confusion, especially if that were the function that caused the exception. And anyone looking at your code would have to realize what you are doing. Also, I bet you used something like __FUNC__ or __FUNCTION__ or __PRETTY_FUNCTION__, which sadly to say are all non-standard (there is no standard way in C++ to get the name of the function).

3. It's slow.
Exception propagation in C++ is already fairly slow, and adding this logic will only make the codepath slower. This is not an issue if you are using macros to catch and rethrow, where you can easily elide the catch and rethrow in release versions of your code. Otherwise, performance could be a problem.

Good practice
While it may not be good practice to catch and rethrow in each and every function to build up a stack trace, it is good practice to attach the file name, line number, and function name at which the exception was originally thrown. If you use boost::exception with BOOST_THROW_EXCEPTION, you will get this behavior for free. It's also good to attach explanatory information to your exception that will assist in debugging and handling the exception. That said, all of this should occur at the time the exception is constructed; once it is constructed, it should be allowed to propagate to its handler... you shouldn't repeatedly catch and rethrow more than stricly necessary. If you need to catch and rethrow in a particular function to attach some crucial information, that's fine, but catching all exceptions in every function and for the purposes of attaching already available information is just too much.

Michael Aaron Safyan
  • 93,612
  • 16
  • 138
  • 200
  • 1
    To add to the "It's slow" case, it also prevents tail-position call optimizations by the compiler. – Hudson Jul 11 '10 at 14:13
  • Actually, the overhead if no exception actually occurs is normally pretty small, and if it does occur (which should be rare) typically is not very important. –  Jul 11 '10 at 14:59
  • @Neil, I was referring to the case where an exception propagates. – Michael Aaron Safyan Jul 11 '10 at 16:50
  • There are zero-cost try implementations, I believe. – Puppy Jul 11 '10 at 18:49
  • @DeadMG If so, I've never encountered one. A reference, please. –  Jul 11 '10 at 19:01
  • @Neil: I've no idea. That's why I believe, rather than I know :P It's merely something that I heard. – Puppy Jul 11 '10 at 19:25
  • @DeadMG: Urban legends are not worth repeating as it reinforces them with no facts. In the beginning C++ exceptions where slow (probably otherwise the myths would not have started) no doubt, but with modern compilers and computers I have never had a problem with speed that has caused my to doubt that exceptions were more than fast enough (and I work with highly scalable SOA were response times are very important). So though I have no proof either way I ignore mythes and will not consider exception speed a problem until I find an explicit case where it is. – Martin York Jul 11 '10 at 19:43
  • @Martin, exceptions are fast enough that they are worth using, but objects need to be destructed when exceptions propagate, so the speed of propagation can't be any faster than the destruction. – Michael Aaron Safyan Jul 12 '10 at 01:39
  • 1
    @Michael Aaron Safyan: The objects need to be destroyed weather exceptions are used or not. So this is a zero sum equation. – Martin York Jul 12 '10 at 18:09
  • boost::exception is such a heavy beast that saying that you "get this behavior for free" is somewhat moot. – Yakov Galka Mar 21 '16 at 11:37
  • But what about exceptions not thrown by myself, for example exceptions thrown from standard library? Unless I wrap every line of standard library calls with a `try-catch-throw`, how can I know where the exception has been triggered? There could be thousands of `at()` which could throw exceptions. – LanYi Sep 18 '19 at 15:08
19

No, it is deeply horrible, and I don't see why you need a call stack in the exception itself - I find the exception reason, the line number and the filename of the code where the initial exception occurred quite sufficient.

Having said that, if you really must have a stack trace, the thing to do is to generate the call stack info ONCE at the exception throw site. There is no single portable way of doing this, but using something like http://stacktrace.sourceforge.net/ combined with and a similar library for VC++ should not be too difficult.

  • 14
    I am talking about a large-scale project. This mechanism helped me many times to see the full flow that caused the exception. Why is it so horrible, if it saves me a lot of debug time? – Igor Jul 11 '10 at 11:45
  • 2
    @Igor And the rest of us of course only work on small-scale projects? It may save you debug time (though if you spend much time debugging you have other problems) but it greatly reduces your code's maintainability and readability which (to me at least) are far more important. –  Jul 11 '10 at 11:46
  • 15
    @Neil : For another point of view, I find stack traces indispensable. @Igor : To generate a stacktrace in gcc, http://stackoverflow.com/questions/77005/how-to-generate-a-stacktrace-when-my-gcc-c-app-crashes , and I'm sure windows has something similar. At any rate, wrapping these blocks around every function really is "deeply horrible". – Stephen Jul 11 '10 at 12:37
  • 3
    Yes a stack trace is good, no building it yourself isn't maintainable. On Windows at least, when you catch an exception you call `GetExceptionInformation` which gives you a `CONTEXT` structure to pass to `StackWalk64`. – Ben Voigt Jul 11 '10 at 16:05
  • @Ben Voigt: Only if you use SEH, instead of /EHsc. – Puppy Jul 11 '10 at 18:46
  • /EHa seems like a very small price to pay for stack traces. – Ben Voigt Jul 11 '10 at 18:58
  • 2
    @Ben The big price you pay is portability, which is a reqiurement in this case. –  Jul 11 '10 at 19:00
  • True. How many locations in the code legitimately handle the exception will determine whether portability is a barrier to using these functions for stack traces in a cross-platform codebase. If there's just one master crash reporting routine, it'll be easy to develop a handful of OS-specific implementations, but if exception handling code is scattered all over, it's a serious concern. – Ben Voigt Jul 11 '10 at 19:36
  • 1
    But then you will see something like `std::out_of_range` with messages like `vector::_M_range_check()`, and you have no idea where is that line of code that triggered the exception, because you may have thousands of `vector::at()` in your project.. – LanYi Sep 18 '19 at 15:04
8

One solution which may be more graceful is to build a Tracer macro/class. So at the top of each function, you write something like:

TRACE()

and the macro looks something like:

Tracer t(__FUNCTION__);

and the class Tracer adds the function name to a global stack on construction, and removes itself upon destruction. Then that stack is always available to logging or debugging, maintenance is much simpler (one line), and it doesn't incur exception overhead.

Examples of implementations include things like http://www.drdobbs.com/184405270, http://www.codeproject.com/KB/cpp/cmtrace.aspx, and http://www.codeguru.com/cpp/v-s/debug/tracing/article.php/c4429. Also Linux functions like this http://www.linuxjournal.com/article/6391 can do it more natively, as described by this Stack Overflow question: How to generate a stacktrace when my gcc C++ app crashes. ACE's ACE_Stack_Trace may be worth looking at too.

Regardless, the exception-handling method is crude, inflexible, and computationally expensive. Class-construction/macro solutions are much faster and can be compiled out for release builds if desired.

Community
  • 1
  • 1
Scott Stafford
  • 43,764
  • 28
  • 129
  • 177
  • If you use this to build your exception and potentially only in debug mode. It would be nice to be able to add parameter information of course. – CashCow Feb 22 '11 at 13:01
  • It's worth noting that even after an exception destructors are called for all constructed objects. This means unless you print the global stack in the function having the exception this model will unravel just like the normal call stack. That being said I still decided to use it but I don't remove things from the stack to avoid the unraveling. I just know that the last thing in the stack is where the error happened (or the closest trace to it). I also added a depth counter that increments on construction and decrements on destruction for tabbing like the example. All in all a good Idea. – Dan Aug 27 '13 at 23:20
  • @Dan yes it will require you to use the trace when you throw and put it into the message. The normal catcher will then be able to see what trace was generated up to that point. – CashCow Nov 21 '13 at 16:06
  • 1
    As for "global stack" it would of course need to be thread-based stack should your code be multi-threaded. – CashCow Nov 21 '13 at 16:07
2

There's a nice little project that gives a pretty stack trace:

https://github.com/bombela/backward-cpp

Scott Stafford
  • 43,764
  • 28
  • 129
  • 177
2

The answer to all your problems is a good debugger, usually http://www.gnu.org/software/gdb/ on linux or Visual Studio on Windows. They can give you stack traces on demand at any point in the program.

Your current method is a real performance and maintenance headache. Debuggers are invented to accomplish your goal, but without the overhead.

Scott Stafford
  • 43,764
  • 28
  • 129
  • 177
1

Look at this SO Question. This might be close to what you're looking for. It isn't cross-platform but the answer gives solutions for gcc and Visual Studio.

Community
  • 1
  • 1
zooropa
  • 3,929
  • 8
  • 39
  • 61
0

One more project for stack-trace support: ex_diag. There are no macros, cross-platform is present, no huge code needs, tool is fast, clear and easy in use.

Here you need only wrap objects, which are need to trace, and they will be traced if exception occurs.

Boris
  • 597
  • 2
  • 10
0

Linking with the libcsdbg library (see https://stackoverflow.com/a/18959030/364818 for original answer) looks like the cleanest way of getting a stack trace without modifying your source code or 3rd party source code (ie STL).

This uses the compiler to instrument the actual stack collection, which is really want you want to do.

I haven't used it and it is GPL tainted, but it looks like the right idea.

Community
  • 1
  • 1
Mark Lakata
  • 19,989
  • 5
  • 106
  • 123
0

While quite a few counter-arguments have been made in the answers here, I want to note that since this question was asked, with C++11, methods have been added which allow you to get nice backtraces in a cross-platform way and without the need for a debugger or cumbersome logging:

Use std::nested_exception and std::throw_with_nested

It is described on StackOverflow here and here, how you can get a backtrace on your exceptions inside your code by simply writing a proper exception handler which will rethrow nested exceptions. It will, however, require that you insert try/catch statements at the functions you wish to trace.

Since you can do this with any derived exception class, you can add a lot of information to such a backtrace! You may also take a look at my MWE on GitHub or my "trace" library, where a backtrace would look something like this:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"
GPMueller
  • 2,881
  • 2
  • 27
  • 36
-2

An exception that isn't handled is left for the calling function to handle. That continues until the exception is handled. This happens with or without try/catch around a function call. In other words, if a function is called that isn't in a try block, an exception that happens in that function will automatically be passed up to call stack. So, all you need to do is put the top-most function in a try block and handle the exception "..." in the catch block. That exception will catch all exceptions. So, your top-most function will look something like

int main()
{
  try
  {
    top_most_func()
  }
  catch(...)
  {
    // handle all exceptions here
  }
}

If you want to have specific code blocks for certain exceptions, you can do that too. Just make sure those occur before the "..." exception catch block.

zooropa
  • 3,929
  • 8
  • 39
  • 61
  • 2
    This doesn't solve the problem of how to generate a stack-trace to help work out *why* the exception was raised. – Oddthinking Jul 11 '10 at 13:21