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 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.