27

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?

skaffman
  • 398,947
  • 96
  • 818
  • 769
Jookia
  • 6,544
  • 13
  • 50
  • 60
  • A small note, if you're not using a smart pointer for logger then this will leak if CVideoLayer's constructor throws. – Adam Bowen Dec 05 '10 at 12:00
  • 1
    I edited the video layer part in as I don't actually have a video layer (yet) and wanted to show my problem. I decided to make it simple rather than accurate. – Jookia Dec 05 '10 at 13:30

4 Answers4

50

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

Ziezi
  • 6,375
  • 3
  • 39
  • 49
Daryl
  • 601
  • 1
  • 5
  • 2
  • 13
    This is the answer I came looking for when I saw the question title in my search engine – d11 Aug 29 '14 at 10:15
20

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;
}
Frédéric Hamidi
  • 258,201
  • 41
  • 486
  • 479
  • 1
    If logger is not a smart pointer (which it should be) then I would also add `catch(...) { delete logger;throw;}` Just in case the CVideoLayer constructor threw another exception. If your object is going to manage more than one resources (pointer) then it needs to use smart pointers otherwise the constructor becomes very complicated to implement correctly. – Martin York Dec 05 '10 at 19:20
  • 1
    is `logger` guaranteed to be NULL if its allocation throws `std::bad_alloc`? because if not you're risking deleting a dangling pointer. – Idan K Dec 05 '10 at 20:37
  • @Idan, excellent point, `logger` would have to be explicitly initialized to `NULL` to be safe. Answer updated accordingly, thanks :) – Frédéric Hamidi Dec 05 '10 at 20:42
  • 1
    generally if you get into the situation where you can't even log what is happening you want to call terminate ASAP anyway – jk. Mar 21 '11 at 14:31
5

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.

Zack Yezek
  • 1,408
  • 20
  • 7
1

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?

MSalters
  • 173,980
  • 10
  • 155
  • 350