I have this code..
CEngineLayer::CEngineLayer(void)
{
// Incoming creation of layers. Wrapping all of this in a try/catch block is
// not helpful if logging of errors will happen.
logger = new (std::nothrow) CLogger(this);
if(logger == 0)
{
std::bad_alloc exception;
throw exception;
}
videoLayer = new (std::nothrow) CVideoLayer(this);
if(videoLayer == 0)
{
logger->log("Unable to create the video layer!");
std::bad_alloc exception;
throw exception;
}
}
IEngineLayer* createEngineLayer(void)
{
// Using std::nothrow would be a bad idea here as catching things thrown
// from the constructor is needed.
try
{
CEngineLayer* newLayer = new CEngineLayer;
return (IEngineLayer*)newLayer;
}
catch(std::bad_alloc& exception)
{
// Couldn't allocate enough memory for the engine layer.
return 0;
}
}
I've omitted most of the non-related information, but I 开发者_开发知识库think the picture is clear here.
Is it okay to manually throw an std::bad_alloc instead of try/catching all of the layer creations individually and logging before rethrowing bad_allocs?
Just to answer the question (since nobody else seems to have answered it), the C++03 standard defines std::bad_alloc
as follows:
namespace std {
class bad_alloc : public exception {
public:
bad_alloc() throw();
bad_alloc(const bad_alloc&) throw();
bad_alloc& operator=(const bad_alloc&) throw();
virtual ˜bad_alloc() throw();
virtual const char* what() const throw();
};
}
Since the standard defines a public constructor, you'd be perfectly safe to construct and throw one from your code. (Any object with a public copy constructor can be thrown, IIRC).
You don't need to do that. You can use the parameterless form of the throw
statement to catch the std::bad_alloc
exception, log it, then rethrow it:
logger = new CLogger(this);
try {
videoLayer = new CVideoLayer(this);
} catch (std::bad_alloc&) {
logger->log("Not enough memory to create the video layer.");
throw;
}
Or, if logger
is not a smart pointer (which it should be):
logger = new CLogger(this);
try {
videoLayer = new CVideoLayer(this);
} catch (std::bad_alloc&) {
logger->log("Not enough memory to create the video layer.");
delete logger;
throw;
} catch (...) {
delete logger;
throw;
}
I personally DO throw it if I use some custom allocator in STL containers. The idea is to present the same interface- including in terms of behavior- to the STL libraries as the default std::allocator.
So, if you have a custom allocator (say, one allocating from a memory pool) and the underlying allocate fails, call "throw std::bad_alloc". That guarantees the caller, who 99.9999% of the time is some STL container, will field it properly. You have no control over what those STL implementations will do if the allocator returns a big fat 0- it is unlikely to be anything you'll like.
Another pattern is to use the fact that the logger is subject to RAII, too:
CEngineLayer::CEngineLayer( )
{
CLogger logger(this); // Could throw, but no harm if it does.
logger.SetIntent("Creating the video layer!");
videoLayer = new CVideoLayer(this);
logger.SetSucceeded(); // resets intent, so CLogger::~CLogger() is silent.
}
This scales cleanly if there are multiple steps. You just call .SetIntent
repeatedly. Normally, you only write out the last intent string in CLogger::~CLogger()
but for extra verbose logging you can write out all intents.
BTW, in your createEngineLayer
you might want a catch(...)
. What if the logger throws a DiskFullException
?
精彩评论