Re: Article on possible improvements to C++
"James Kanze" <email@example.com>
A new way I've been thinking about RAII is the following: All
"free resource calls", like delete, release mutex, close
connection, etc., should be made only inside destructors.
There are two major problems with this: it doesn't always apply,
Possibly it is just a wrong selection of wording.
I wrote many manager classes and read implementation of stock ones -- IME it
is rare to put the release code in the dtor. And too often I put it there
first just to move away very soon. To a private function like Dispose (I
would call it release, but it that got used for a different thing), and call
that from dtor.
And it is needed for other functions like reset() or operator =.
There is some beauty in the simplest RAII form -- the manager is NOCOPY and
has absolutely nothing but ctor and dtor. It really leaves no chance at all
to mess up. But I used those only for mutex-lock/flag/counter guards that
are so absolutely tied to a scope block and such simple use case.
For many other situations I just used a policy-based smart pointer
(interface functions like that of auto_ptr, have a nocopy and a deepcopy
variant, and the payload of Dispose mentioned above is injected as policy).
Normally I'm against having funtions in the interface that are not known to
be needed up front, but this looks like exception. Possibly because the
mentioned functions are getting special attention in review anyway.
and it only concerns resources. What you're concerned about is
program coherence: freeing a mutex lock (in a destructor) when
you've left the locked data in an inconsistent state, for
example, is not going to help. You must ensure that the
destructor also leaves the object in a coherent state. (This is
what is often meant by exception safety, but it really concerns
anything which might affect program flow.)
Yeah, this is a way tougher problem than maintain symmetry of alloc
functions. In general at least. There are ways to shove off some of the
burden by sticking to just basic guarantees, and compensate by discarding a
major context entirely on exception. So stronger guarantees are needed only
at handling the few objects that are beyond that and mutable by the thing --
ideally nothing or just a few.
Specifically, all resources at all times should have a clearly
identified owner who frees the resource in its destructor, or
the resource is a stack object.
This is fine to a point, but I am supposing that the "identified
owner" is an object, and instance of a class. If that object is
dynamically allocated, and you're considering memory as a
resource, then you have a vicious circle.
Precisely you have the circle if your if is true indefinitely in the
recursion. In practice there will be a roof. If you stick to the 'every
allocation function can happen only in an ownership-taking parameter place'
you will naturally discover should you missed a real circle.
It's easy to extend this idea to shared ownership. Optionally,
you can free resources early as long as the resource still has
an owner which would still free the resource if you
"accidentally" commented out the early release.
The idea is that maintaining the invariant of "ownership"
using destructors produces easier to follow code, and less
leaky code. RAII is all about ownership responsibilities. I
haven't taken enough time to look through all examples, so
please treat this as a tentative idea from myself. Obviously
there will be exceptions to this rule, I think.
The problem is that in many cases, the "owner" of an object is
the object itself. The whole basis of OO is that objects have
behavior; that behavior often determines their lifetime. (It's
not for nothing that most deletes in well written code are
"delete this", at least in some application domains.)
Actually I heard this statistic (though many times) only from a single
person, you, James. While anywhere I looked delete this had zero instances
or very few. Including the well-written programs. So I have
reservations, that it is a tech for indded "some" domains, and not the rest.
Say if a program processes text (and for whatever reason cose C++ instead of
....) does it realisticly need delete this anywhere?
Now as I think of it, MFC has a plenty of classes with self-deleting, and it
makes sense indeed, as part of the framework. And I used those classes
extensively too (stock or as parent classes), but hardly ever needed to do
the same in mine directly. So I tend to not count'em as part of *my*
application, maybe that creates the difference. ;-)