Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When a RAII object fails to construct

Tags:

c++

raii

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.

like image 708
wilhelmtell Avatar asked Oct 31 '10 15:10

wilhelmtell


People also ask

What is a RAII object?

Resource Acquisition Is Initialization or RAII, is a C++ programming technique which binds the life cycle of a resource that must be acquired before use (allocated heap memory, thread of execution, open socket, open file, locked mutex, disk space, database connection—anything that exists in limited supply) to the ...

What are RAII principles?

The principle that objects own resources is also known as "resource acquisition is initialization," or RAII. When a resource-owning stack object goes out of scope, its destructor is automatically invoked. In this way, garbage collection in C++ is closely related to object lifetime, and is deterministic.

What does RAII stand for?

Resource acquisition is initialization (RAII) is a programming idiom used in several object-oriented, statically-typed programming languages to describe a particular language behavior.

Why is RAII important?

RAII is an important C++ idiom for resource management. Notably, RAII provides a structural idiom for proper resource management with exceptions. The power of the idiom is in the guarantees it provides. Properly used, the destructor for your RAII-object is guaranteed to be called to allow you to free resources.


2 Answers

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.

like image 84
Steve Jessop Avatar answered Sep 19 '22 06:09

Steve Jessop


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!
like image 40
Johannes Schaub - litb Avatar answered Sep 18 '22 06:09

Johannes Schaub - litb