简体   繁体   中英

How to use std::exception -- stack trace plus memory leak

I am trying to figure out the best way to deal with exceptions.

C++ says that any datatype can be thrown as an exception, but there is no way to tell what data type has been thrown, which makes exceptions essentially worthless. However, I am assuming that these days everyone throw std::exception, so that is safe in a catch. Is that a fair assumption?

(I am building a library that will be used by others, so has to fit in with other code that I do not control.)

The Exception.what function returns a char * (not a string) with no way to free it. Of course, that string should contain information about the exception, for example if a file is not found then the name of the file that was not found. I assume that we just tolerate a small memory leak here when exceptions are thrown. Correct?

It is unclear how reliably stack traces can be generated in a general way and sent to a log file. (In a production environment when things go wrong, not in a debugger.) So I was thinking of inserting otherwise redundant try/catch blocks at interesting places in the code which can add logging and append extra information to the what text and then rethrow a new exception. But that process will, of course, destroy any stack trace. For example

foo = stuff();
try {
  processStuff()
} catch (std::exception& ex) {
  string msg = "While processing " + foo.toString() + ": " + ex.what;
  log << msg;
  throw std::exception((char[])msg);
}

Is that the best approach?

There are lots of articles that describe the basic classes but nothing I could find on how to really make them work in practice. Links appreciated.

(Here again I am trying to write Java/.Net in C++. Is that a hopeless cause?)

Type of exception
What type of exception object can be thrown is part of the function / method's public contract.

Often, libraries may introduce their own exception types which may or may not inherit from std::exception .

Recommended: throw std::exception or an exception inherited from it. Document the type(s) of exception thrown. Usually, you will have a "library default", such as

Unless stated otherwise, all methods and functions in this library may throw an exception of type mylib::exception or a type inherited from it.

char * what()
The (implied?) assumption is that the pointer is valid as long as the exception object is valid. No necessity to free the string manually, no reason for leak.

If you need to store the text for later, make a copy of it - eg by assigning to std::string .

Stack traces
... are not part of the C++ standard. Furthermore, most compiler implementations allow generating code that does not allow re-discovering a stack trace. (the respective option usually says something about "stack frame").

However, for most compilers, libraries exist that can add this stack trace, but this usually requires access to the debug symbols.

"Redundant" try/catch
You might need them anyway.
The perfect exception must support different aspects:

  • it contains instructions or hints for the end user how to remove a blocking issue ( "Cannot open file bibbit.txt because it is read only" )

  • it contains information for support and development why the error occurred, allowing them to possibly avoid it / handle it better in the future. (Such as ... the stack trace)

  • it needs to allow calling code to detect known exceptions and handle them specifically (eg the file the user wants to open is read-only, ask user if they want to open in read-only mode, and try again )

Rarely an exception does all that perfectly. But repackaging at certain "layers" helps a lot here - even though this makes us lose some of the beauty of exceptions.

Does that help?


Edit regarding your edit:

Generally ok, but DO NOT re-construct the exception. Change your code to:

 ...
 catch (std::exception& ex) {
  string msg = "While processing " + foo.toString() + ": " + ex.what;
  log << msg;
  throw;  // <--- !!!!!!
}

This re-throws the original exception, so catch handlers further down can still distinguish the different types:

  void Foo() {
     class random_exception : public std::exception { ... }
     try {
        ...
        if (rand() % 2) throw random_exception();
        ...
     }
     catch(std::exception const & x)
     {
        log << x.what();
        throw;
     }
   } 

   int main()
   {
      try {
        Foo();
      }
      catch(random_exception const & x)
      {
         cout << "A random exception occurred, you can try again";
      }
      catch(std::exception const & x)
      {
         cout << This didn't work. " << x.what(); 
      }
      catch(...) // catches all exception types, but you don't know what
      {
         cout << "Uh, oh, you are doomed.".
      }
   }

Of course, that's an issue when repackaging the exception.


Some things I'm leaning to:

The exception type to be thrown is a thin wrapper around a (smart) pointer. This allows storing exceptions (eg to pass them to another thread, or as an "inner exception" when repackaging) without losing type information:

I allow separate messages for end user and for diagnostics. End user message is required, diagnostics is usually generated from (and contains) the error code and context. User will see no "weird numbers and techie gibberish" unless they click on "details".

Instead of including parameters in the error message:

msg = "Could not open file bibbit.txt (tried 5 times)"; // :-(

I use a fixed message, and a list of (name, value) parameters.

msg = "Could not open file";  // :-)
msg.Add("filename", filename).Add("retries", retryCount);

This should simplify localization, and allows the caller to access individual attributes for known errors, allowing more specific handling.

However, I am assuming that these days everyone throw std::Exception, so that is safe in a catch. Is that a fair assumption?

That's mostly a safe assumption. Some people insist in throwing random stuff, but it's not your business - if you are a library you throw exceptions and at most you catch your own, you shouldn't be concerned with what other people throw (your code should use RAII anyway to avoid resource leaks - this covers even the case where eg callbacks throw stuff you don't know).

The Exception.what function returns a char * (not a string) with no way to free it. Of course, that string should contain information about the exception, for example if a file is not found then the name of the file that was not found. I assume that we just tolerate a small memory leak here when exceptions are thrown. Correct?

Nope. The string returned by what normally does not result in a memory leak. Either is statically allocated (I've seen many simple exception classes that just return a string literal) or it's managed inside the exception itself ( std::runtime_error normally contains an std::string , with what returning the result of its c_str method). In general, you can assume that whatever what results is there as long as the exception lives - if you need it outside the catch block copy it into an std::string or something.

This is actually required by the standard:

The return value remains valid until the exception object from which it is obtained is destroyed or a non-const member function of the exception object is called.

(C++11, §18.8.1 ¶10)

It is unclear how reliably stack traces can be generated in a general way and sent to a log file. (In a production environment when things go wrong, not in a debugger.) So I was thinking of inserting otherwise redundant try/catch blocks at interesting places in the code which can add logging and append extra information to the what text and then rethrow a new exception. But that process will, of course, destroy any stack trace.

Stack traces in C++ are a sad story; there's no portable way to generate them, you have to resort to platform-specific calls (or to libraries that abstract them); personally I used several approaches in the past:

  • on a new, multi-platform Qt application I wrote my own "fat" exception class, which saved the stack trace (I derived from booster::exception , which already bundles the stack trace-saving part) and added the possibility to add extra information while the exception gets caught and rethrown, similar to how Boost.Exception is implemented (important: Boost.Exception != booster::exception, they are completely unrelated); see here , it's full of Qt types but you can get the idea;
  • on a legacy project which throw the most bizarre types (and there's no way to change them to a common exception type) I just hooked (via linker tricks) the throw and I always save the stack trace in a global circular buffer, which is printed out if an exception is not caught.

Your approach is flawed because not only it loses parts of the stack trace, but even the type information about the exception. Look up how Boost.Exception does its thing to see how to do it right. Also, you never, ever throw a new (=heap allocated) exception, otherwise freeing it becomes a burden to people who want to catch it (also, probably nobody will catch it because no one catches by pointer, you normally catch by reference).

It looks like std::stack_trace will be part of C++ 23.

Under gcc (yes, I know, not universal), you can use std::current_exception to get information about the current exception. For instance, the following code will return the mangled name of the exception type:

std::string current_exception_type()
{
    std::string result;

    if (std::current_exception())
    {
        auto p = std::current_exception().__cxa_exception_type();
        if (p)
        {
            result = p->name();
        }
    }

    return result;
}

To demangle the name, you can use:

std::string demangle_cpp(char const* name)
{
    std::string result;
    char*   demangled(nullptr);

    try
    {
        int status;
        demangled = abi::__cxa_demangle(name, 0, 0, &status);
        if (demangled != nullptr) result = demangled;
    } catch(...)
    {
        
    }

    if (demangled != nullptr) free(demangled);

    return result;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM