Question

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?

Was it helpful?

Solution

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;
}

OTHER TIPS

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

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?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top