开发者

std exceptions inviting unsafe usage?

It is recommended that you always throw something derived from std::exception and there are a few predefines specialisations such as std::runtime_error

std::exception's interface is given in terms of non-throwing accessors. Great. Now look at the constructor for 开发者_如何转开发std::runtime_error

class runtime_error : public exception {
public:
  explicit runtime_error (const string &);
};

So if I do this

try {
    foo ();
}
catch (...) {
    throw std :: runtime_error ("bang");
}

it's entirely possible that foo threw because it's out of memory, in which case constructing the string argument to runtime_error can also throw. This would be a throw-expression which itself also throws: won't this will call std::terminate?

Doesn't this mean we should always do this instead:

namespace {
    const std :: string BANG ("bang");
}

...

try {
    foo ();
}
catch (...) {
    throw std :: runtime_error (BANG);
}

BUT WAIT this won't work either, will it? Because runtime_error is going to copy its argument, which may also throw...

...so doesn't this mean that there is no safe way to use the standard specialisations of std::exception, and that you should always roll your own string class whose constructor only fails without throwing?

Or is there some trick I'm missing?


I think your main problem is that you are doing catch(...) and translating to a std::runtime_error thereby losing all type information from the original exception. You should just rethrow with throw().

Practically, if you are short of memory you are likely have a bad_alloc exception thrown at some point and there's not a lot else you can - or should - do. If you want to throw an exception for a reason other than an allocation failed then you are not likely to have a problem constructing a sensible exception object with meaningful contextual information. If you hit a memory issue while formatting your exception object there's not a lot you can do other than propagate the memory error.

You are right that there is a potential problem if you construct a new string object to construct an exception, but if you want to format a message with context this can't be avoided in general. Note that the standard exception objects all have a const char* constructor (as of last week) so if you have a const char* that you want to use you don't have to construct a new std::string object.

std::runtime_error must copy it's argument, but not necessarily as a new string object. There could be an area of statically allocated memory which it can the contents of its argument to. It only has to fulfil the what() requirements which only requires returning a const char *, it doesn't have to store a std::string object.


This would be a throw-expression which itself also throws: won't this will call std::terminate?

No, it wouldn't. It would just throw the exception about insufficient memory. The control will not reach the outer throw part.

BUT WAIT this won't work either, will it? Because runtime_error is going to copy its argument, which may also throw...

Exception classes with a throwing copy-constructors are as evil as throwing destructors. Nothing that can really be done about it.


std::runtime_error is designed to treat the usual runtime errors, not out of memory or other such critical exceptions. The base class std::exception does not do anything which may throw; nor does std::bad_alloc. And obviously, remapping std::bad_alloc into an exception which requires dynamic allocation to work is a bad idea.


The first thing is what would you want to do if you happen to have a bad_alloc exception because you're out of memory?

I'd say in a classic C++ program, you'd want to have the program somehow trying to tell you what happened and then terminates.

In a classic C++ program you'd then let the bad_alloc exception propagate to the main section of the program. The main will contain an arragement of try/catch like this:

int main()
{
   try
   {
      // your program starts
   }
   catch( const std::exception & e )
   {
       std::cerr << "huho something happened" << e.what() << std::endl;
   }
   catch( ... )
   {
       std::cerr << "huho..err..what?" << std::endl;
   }
}

you'll only use catch( ... ) inside the main and at the starting functions of threads. Contrary to some other languages like Java you're not expected to catch all possible exceptions locally. You just let them propagate until you catch them where you wanted to.

Now if you have code that specifically must check std::bad_alloc, you should only catch( const std::bad_alloc & ) locally. And there it should maybe wise to do something else rather than just rethrow another exception.

I found in The C++ Programming Language §14.10 also that the C++ exception-handling mechanism keeps a bit of memory to itself for holding exceptions, so that throwing a standard library exception will not throw an exception by itself. Of course it is possible also to let the exception-handling mechanism run out of memory if you really code something perverted.

So, to sum up, if you do nothing and let big exceptions like bad_alloc propagate nicely where you want to catch them, in my opinion you're safe. And you should not use catch( ... ) or catch(const std::exception & ) anywhere except in the main function and in the starting functions of threads.

Catching all exceptions to rethrow a single exception is really the last thing to do. You lose every advantages you got with the C++ exception-handling mechanism.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜