Bjarne Stroustrup writes in his C++ Style and Technique FAQ, emphasis mine:
Because C++ supports an alternative that is almost always better: The "resource acquisition is initialization" technique (TC++PL3 section 14.4). The basic idea is to represent a resource by a local object, so that the local object's destructor will release the resource. That way, the programmer cannot forget to release the resource. For example:
class File_handle { FILE* p; public: File_handle(const char* n, const char* a) { p = fopen(n,a); if (p==0) throw Open_error(errno); } File_handle(FILE* pp) { p = pp; if (p==0) throw Open_error(errno); } ~File_handle() { fclose(p); } operator FILE*() { return p; } // ... }; void f(const char* fn) { File_handle f(fn,"rw"); // open fn for reading and writing // use file through f }
In a system, we need a "resource handle" class for each resource. However, we don't have to have an "finally" clause for each acquisition of a resource. In realistic systems, there are far more resource acquisitions than kinds of resources, so the "resource acquisition is initialization" technique leads to less code than use of a "finally" construct.
Note that Bjarne writes "almost always better" and not "always better". Now for my question: What situation would a finally
construct be better than using the alternative construct (RAII) in C++?
The difference between them is that destructors emphasise reuse of the cleanup solution by associating it with the type being used, whereas try/finally emphasises one-off cleanup routines. So try/finally is more immediately convenient when you have a unique one-off cleanup requirement associated with the point of use, rather than a reusable cleanup solution that can be associated with a type you're using.
I haven't tried this (haven't downloaded a recent gcc for months), but it should be true: with the addition of lambdas to the language, C++ can now have the effective equivalent of finally
, just by writing a function called try_finally
. Obvious usage:
try_finally([]
{
// attempt to do things in here, perhaps throwing...
},
[]
{
// this always runs, even if the above block throws...
}
Of course, you have to write try_finally
, but only once and then you're good to go. Lambdas enable new control structures.
Something like:
template <class TTry, class TFinally>
void try_finally(const TTry &tr, const TFinally &fi)
{
try
{
tr();
}
catch (...)
{
fi();
throw;
}
fi();
}
And there is no link at all between the presence of a GC and a preference for try/finally instead of destructors. C++/CLI has destructors and GC. They're orthogonal choices. Try/finally and destructors are slightly different solutions to the same problem, both deterministic, needed for non-fungible resources.
C++ function objects emphasise reusability but make one-off anonymous functions painful. By adding lambdas, anonymous code blocks are now easy to make, and this avoids C++'s traditional emphasis on "forced reusability" expressed through named types.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With