tags:

views:

142

answers:

4

Suppose I construct a RAII object, and that object may fail to construct. How do I handle this?

try {
    std::vector<int> v(LOTS);
    // try scope ends here because that's what the catch is for
} catch( const std::bad_alloc& ) {
    // ...
}
// v? what v?

Granted, the default constructor of std::vector won't throw and that can help, but this is not the general case. A constructor may very well throw. If I want to handle any resource acquisition failure, how do I do that while still being able to proceed if it doesn't throw?

Edit: To clarify, my issue is that if a resource fails to acquire then I might want to try again, and so on. Maybe I can try acquiring an alternative resource.

+7  A: 

If an RAII constructor throws, all resources bound to RAII objects prior to the throwing point will be cleaned up properly. The C++ rules are sensibly designed to guarantee that.

If your v construction throws because of a bad_alloc then any RAII object created prior to v in the try block will be properly cleaned up.

So if you consequently use RAII, you don't need a manual try / catch like that, because the RAII objects handle cleanup for you. If you do need it for some reason, in the case above you could use swap like the following.

std::vector<int> v;
try {
    std::vector<int> vtry(LOTS);
    v.swap(vtry); // no-throw
} catch( const std::bad_alloc& ) {
    // ...
}
// v!
Johannes Schaub - litb
This isn't about cleaning up. It's about doing something when the RAII object fails to initialize. If I do nothing then the exception propagates. No good. What if my function must provide a no-throw guarantee? What if the RAII object doesn't have a constructor that won't throw?
wilhelmtell
You may well say that an object that has no constructor that doesn't throw is badly designed. That's fine. I just never saw this recommendation anywhere, so if that's true I want to bring it to the surface. Otherwise, hear how you handle the situation when any ctor can throw.
wilhelmtell
@wilhelmtell, "What if the RAII object doesn't have a constructor that won't throw?" -> I think you need to put the entire code within the `try` clause then. I don't think that it is bad design to not have a no-throw constructor. Sometimes it may not fit well. But I find it useful if such a no-throw state exists. You can also wrap your object into a smart-pointer that has a nothrow default state (like `shared_ptr`). But I think that would be disgusting. Try blocks have zero-execution-cost in C++, if done properly with tables.
Johannes Schaub - litb
+6  A: 

Depends what you mean by "proceed". Whatever operation requires the resource will fail: that's what "requires" means. So when you want to continue after an error, you might end up writing code like this:

void something_using_RAII(thingummy &t) {
    vector<int> v(t.size_required);
    // do something using v
}

...

for each thingummy {
    try {
         something_using_RAII(this_thingummy);
    } catch(const std::bad_alloc &) {
         std::cerr << "can't manage that one, sorry\n";
    }
}

That's why you should only catch exceptions when there's something worthwhile you can do with them (in this case, report failure and move on to the next thingummy).

If you want to try again on failure, but only if the constructor fails, not if anything else fails:

while(not bored of trying) {
    bool constructor_failed = true;
    try {
        vector<int> v(LOTS);
        constructor_failed = false;
        // use v
    } catch(...) {
        if (!constructor_failed) throw;
    }
}

This is more-or-less how std::new_handler works - the handler is called in the catch clause of a similar loop, although with no need for a flag.

If you want to try a different resource on failure:

try {
    vector<int> v(LOTS);
    // use v
} catch(...) try {
    otherthing<int> w(LOTS);
    // use w
} catch(...) {
    // failed
}

If "use v" and "use w" are basically the same code, then refactor into a function and call it from both places. Your function is doing quite a lot at this point.

Steve Jessop
`+1` alone for that last sentence. Not enough programmers pay attention to that. Very unfortunately.
sbi
+2  A: 

If v can't be created, all the code that tries to use v can't be executed. Move the catch after the code that code uses v, in a place where it is reasonable to continue execution if there is no v.

sth
The problem, as I understand it, is that then you can't distinguish in the catch from an exception thrown by v's ctor or by the other code in the try block.
Roger Pate
@Roger: in which case (barring litb's solution, which is fine but relies on `swap`, which not all RAII classes have) you have to write something annoying like `try { vector<int> v(LOTS); bool flag = true; try { use v; flag = false; } catch (...) { X } } catch(...) { Y }`. X handles exceptions from the other code in the try block, Y handles exceptions from the constructor *or destructor* of `v`, and from X. But the destructor of `v` shouldn't throw, and exceptions from X can be recognised by the flag.
Steve Jessop
A: 

All code that uses v needs to be in the try block. If the question is how to then narrow down the code which threw the exception, you can use some kind of flag to indicate where in the try block you are, like this:

string flag;
try
{
    flag = "creating vector<int> v";
    std::vector<int> v(LOTS);

    flag = "performing blaggity bloop";
    blaggity_bloop();

    flag = "doing some other stuff";
    some_other_stuff();
}
catch( const std::bad_alloc& )
{
    cerr << "Bad allocation while " << flag << endl;
}
PigBen