Resource Acquisition is Initialization (RAII) is commonly used in C++ to manage the lifetimes of resources which require some manner of cleanup code at the end of their life
A dedicated scope guard mechanism can cleanly and concisely manage C-style resources. Since it's a relatively old concept, there are a number floating around, but scope guards which allow arbitrary code execution are by nature the most flexible of them. Two from popular libraries are SCOPE_EXIT, from facebook's open source library folly (discussed in Andrei Alexandrescu talk on Declarative Control Flow), and BOOST_SCOPE_EXIT from (unsurprisingly) Boost.ScopeExit.
folly's SCOPE_EXIT is part of a triad of declarative control flow functionality provided in SCOPE_EXIT SCOPE_FAIL and SCOPE_SUCCESS respectively execute code when control flow exits the enclosing scope, when it exits the enclosing scope by throwing an exception, and when it exits without throwing an exception.1
If you have a C-style interface with a resource and lifetime management functions like this:
struct CStyleResource; // c-style resource
// resource lifetime management functions
CStyleResource* acquireResource(const char *, char*, int);
void releaseResource(CStyleResource* resource);
you may use SCOPE_EXIT like so:
#include
// my code:
auto resource = acquireResource(const char *, char *, int);
SCOPE_EXIT{releaseResource(resource);}
Boost.ScopeExit has a slightly differing syntax.2 To do the same as the above code:
#include
// my code
auto resource = acquireResource(const char *, char *, int);
BOOST_SCOPE_EXIT(&resource) { // capture resource by reference
releaseResource(resource);
} BOOST_SCOPE_EXIT_END
You may find it suitable in both cases to declare resource as const, to ensure you don't inadvertently change the value during the rest of the function and re-complicate the lifetime management concerns you're trying to simplify.
In both cases, releaseResource will be called when control flow exits the enclosing scope, by exception or not. Note that it will also be called regardless of whether resource is nullptr at scope end, so if the API requires cleanup functions not be called on null pointers you'll need to check that condition yourself.
The simplicity here versus the use of a smart pointer comes at the cost of not being able to move your lifetime management mechanism around the enclosing program as easily as you can with smart pointers, but if you want a dead-simple guarantee of cleanup execution when the current scope exits, scope guards are more than adequate for the job.
1. Executing code only on success or failure offers a commit/rollback functionality which can be incredibly helpful for exception safety and code clarity when multiple failure points may occur in a single function, which seems to be the driving reason behind the presence of SCOPE_SUCCESS and SCOPE_FAIL, but you're here because you're interested in unconditional cleanup.
2. As a side note, Boost.ScopeExit also doesn't have built-in success/fail functionality like folly. In the documentation, success/fail functionality like that provided by the folly scope guard is instead implemented by checking a success flag that has been captured by reference. The flag is set to false at the start of the scope, and set to true once the relevant operations succeed.