1

I wrote a routine to store the backtrace and line no and file name etcetera. The purpose for this was to store such data for whenever an exception is thrown. However, the problem I am facing is that my routine will be called from the catch block and it will end up storing the backtrace up to the catch block. This is not good. I only must add the backtrace till the place where the exception is thrown. I cannot (obviously call it inside the try block since in that case I will end up storing a backtrace even in the cases where no exception is thrown). I could also always store the backtrace to the end of the try block and access it inside the catch block; but there is no way of knowing at which line of the try block the exception will be thrown. Thus the throw function seems to be a good place to add the routine call. But I dont know how to do it. Please help me.

If my strategy seems inherently wrong, please feel free to point me a better solution. If the problem itself is not clear, please leave a comment.

P.S. I wrote the custom exception class to inherit from std::runtime_error.

Chani
  • 5,055
  • 15
  • 57
  • 92
  • 1
    Are you responsible for throwing these exceptions? You could derive classes from `std::exception` that generate a trace in their constructor. – Drew Dormann Jun 05 '13 at 17:05
  • @DrewDormann I am indeed deriving from `std::runtime_error` which is a subclass of `std::exception`. Can you please elaborate ` derive classes from std::exception that generate a trace in their constructor` – Chani Jun 05 '13 at 17:07
  • Which platform and compiler? – Nicholaz Jun 05 '13 at 17:14
  • @DrewDormann Ok, I think I understand what you said now. But again, the call to the constructor is going to be one call away from the location of the exception and I need that particular location (the location of the throw). Can you please suggest a way to do this ? – Chani Jun 05 '13 at 17:14
  • @Nicholaz g++ 4.7 (C++11x) on Linux – Chani Jun 05 '13 at 17:15
  • 1
    @DrewDormann I realise you are correct. I will implement like you said and see what happens. I think it must work. Thanks ! – Chani Jun 05 '13 at 17:19

3 Answers3

4

There is no 'throw function' defined by C++ that you can override. Throwing is handled by the C++ implementation and there's no standard way to insert any kind of code for every throw.

Instead what you can do is make your exception type store the current backtrace when it is constructed.

std::string get_backtrace() {
    return "this is the backtrace...";
}

struct backtrace_exception : public std::exception {
    std::string b;

    backtrace_exception() : b(get_backtrace()) {}
};

int main() {
    try {
        throw backtrace_exception();
    } catch(backtrace_exception &e) {
        std::cout << e.b;
    }
}
bames53
  • 86,085
  • 15
  • 179
  • 244
1

You cannot overload the throw operator. The more usual solution would be to define a macro that packages the exception with a backtrace record. For example this:

#include <string>
#include <iostream>
#include <sstream>
#include <exception>
#include <stdexcept>
#include <type_traits>

template <typename BaseException>
class backtraced_exception : public BaseException {
  private:
    std::string backtrace;
  public:

    template <typename... Args>
    backtraced_exception(const char* aFilename, int aLineNum, Args&&... args) :
      BaseException(std::forward<Args>(args)...) {
      std::stringstream ss;
      ss << "From '" << aFilename << "' at line " << aLineNum << ":\n" 
         << BaseException::what();
      backtrace = ss.str();
    };

    backtraced_exception(const std::exception& e, const char* aFilename, int aLineNum) :
      BaseException(static_cast<const BaseException&>(e)) {
      std::stringstream ss;
      ss << "From '" << aFilename << "' at line " << aLineNum << ":\n" 
         << e.what();
      backtrace = ss.str();
    };

    virtual ~backtraced_exception() noexcept { };

    virtual const char* what() const noexcept {
      return backtrace.c_str();
    };
};

#define THROW_WITH_BACKTRACE(EXCEPTION, ARG1) throw backtraced_exception< EXCEPTION >(__FILE__, __LINE__, ARG1)
// ... and you can create more macros for more arguments...

#define CATCH_WITH_BACKTRACE(EXCEPTION, EXCEPT_NAME) catch(backtraced_exception< EXCEPTION >& EXCEPT_NAME)

#define RETHROW_WITH_BACKTRACE(EXCEPT_NAME) throw backtraced_exception< std::decay< decltype(EXCEPT_NAME) >::type >(EXCEPT_NAME, __FILE__, __LINE__)

Use it like this:

int main() {
  try {
    try {
      try {
        THROW_WITH_BACKTRACE(std::runtime_error, "This is an example!");
      } CATCH_WITH_BACKTRACE(std::runtime_error, e) {
        std::cout << "First caught this exception:\n" << e.what() << std::endl;
        RETHROW_WITH_BACKTRACE(e);
      };
    } catch(std::runtime_error& e) {  // can also catch normally.
      std::cout << "Got this exception:\n"
                << e.what() << std::endl;
      // and even rethrow again, with backtrace:
      RETHROW_WITH_BACKTRACE(e);
    };
  } catch(std::runtime_error& e) {
    std::cout << "Finally, got this exception:\n"
              << e.what() << std::endl;
  };
};

The output is as follows:

First caught this exception:
From 'logged_except.cpp' at line 50:
This is an example!
Got this exception:
From 'logged_except.cpp' at line 53:
From 'logged_except.cpp' at line 50:
This is an example!
Finally, got this exception:
From 'logged_except.cpp' at line 59:
From 'logged_except.cpp' at line 53:
From 'logged_except.cpp' at line 50:
This is an example!

Another good thing with this solution is that you can disable backtracing by simply conditionally defining the macros depending on whether you want backtracing or not (e.g. debug or release build).

The above example requires C++11 features, but you can probably come up with an equivalent solution without those features (i.e., variadic templates, decltype, type-traits, etc.).

And you could also use C++11 exception-pointers to make this scheme even more convenient.

Mikael Persson
  • 18,174
  • 6
  • 36
  • 52
1

Yeah you can override 'throw' under GCC by overriding this function :

extern "C" void __cxa_throw (void *thrown_exception, void *pvtinfo, void (*dest)(void *))

After doing your stuff you should call the real 'throw' for that you should use 'dlsym()' under Unix to get its address or use function wrapping under 'mingw' by passing -WL,-wrap,symbole to the linker , like in here GNU gcc/ld - wrapping a call to symbol with caller and callee defined in the same object file , I worked with both methods

Dhia Hassen
  • 508
  • 4
  • 20