I\'m always reading that I should not to throw a std::string
or some other classes allocating memory. like here or more importantly here on point 3. - D
The advice is basically telling you "Don't use any construct that might throw an exception in an exception". That's because if you get an exception while trying to throw an exception, the C++ runtime will just immediately call terminate()
and kill your program.
Now if (either) of the exceptions involved would just call terminate()
anyways (as is the default for an uncaught exception), then you don't really need to worry about it. For example, if your application can't handle bad_alloc
(can't recover from out-of-memory), then you don't need to worry about copy constructors (such as std::string
) that might throw it.
But if you want to be able to catch and recover from a bad_alloc
, you need to ensure that none of your exception copy constructors can cause one. If you're writing a library that other applications will use, you should not assume that the application does not want to handle bad_alloc
.
C++11 make this much easier by using move constructors (instead of copy constructors) where possible. Since the move constructor for std::string
never throws exceptions you can safely use a std:string
in your exception type as long as you properly implement move constructors, and ensure that they are used. Note that the initial construction of the object to be thrown in a throw expression is NOT part of the exception throwing process, so that constructor can throw an exception without causing a double exception (and terminate()). So if you have:
throw some_function();
some_function
might throw an exception (such as bad_alloc
) without returning an object to be thrown and that's fine. If it doesn't throw an exception (and returns a valid object), the move constructor for the exception type will be used (if available) for the exception throwing process, and that move constructor must not throw an exception.
Completely independent of the above, whenever you call new
you need to ensure that exactly one spot will call delete
in every possible case, or you'll leak memory (or crash from a double delete). This becomes tricky any time you have a function that calls new
and then does something else that might throw an exception (such as call new
again). If this happens in a constructor, the destructor for the object will not be called (though destructors for base classes and fields will be), so you can't do the cleanup in the destructor as you are trying to do with your example.
Fortunately std::unique_ptr
exists to make this much easier. If you write your exception class as:
struct xexception {
std::unique_ptr<int[]> ttt[10];
xexception() {
ttt[0].reset(new int[0xfffffffL]);
ttt[1].reset(new int[0xfffffffL]);
ttt[2].reset(new int[0xfffffffL]);
ttt[3].reset(new int[0xfffffffL]);
ttt[4].reset(new int[0xfffffffL]);
ttt[5].reset(new int[0xfffffffL]);
ttt[6].reset(new int[0xfffffffL]);
ttt[7].reset(new int[0xfffffffL]);
ttt[8].reset(new int[0xfffffffL]);
ttt[9].reset(new int[0xfffffffL]);
}
};
it should work and not leak memory.
While I think not using std::string
for core, fundamental exceptions may be a good guideline, I don't think user-facing libraries/applications should necessarily follow this.
There may be other reasons, but you hit on the primary one: you'd like to indicate to the user (or developer) contextually meaningful information, which you often cannot do with a mere literal string. A dynamic allocation must occur in order to do this. If, for some reason, you had a bad_alloc
, you're probably already hosed to begin with, so it doesn't buy/lose you anything.
Edit:
By the way: the destructor of std::exception
is marked as virtual for a reason!