Re: Article on possible improvements to C++
"Nick Keighley" <nick_keighley_nospam@hotmail.com>
As I'm not a C++ expert I was wondering if this post (and others in
this thread) was going to earn me a Larting. I do however have
opinions (opinions are like...) on softwasre developmne tand I have
developed and maintained C++ systems.
I just get bugged by the "RAII solves all memory management problems"
line and the implicit assumption that all objects follow a simple FIFO
allocation/deallocation protocol. Don't these people ever tackle real
world problems!
Not sure how you arrived at the everything is FIFO assumption.
I was stating to use managers -- and that managers can be non-locals. After
all we don't have so much places, so if you have a manager it can be:
- local in a block in the immediate function
- local in a block in a function somewhere up the call chain
- a global(/singleton)
you put responsibility of deallocation to the manager, and it will
eventually happen for all of them. There is a chance for pseudo-leaks, we
probably not yet discussed. Like when you have a list with long life, keep
adding nodes and never remove any. Stuff can get stuck in managers that are
global or live wery much up (say right in main()). Getting the resources
released only at prog exit may be not good enough, and act like a regular
leak in all practical terms. No silver bullets. Think that is hardly news
to anyone reading this group.
And pseudo-leaks are damn hard to discuss in abstract, in a forum: how you
tell whether some such node is yet needed in a system or not? Except by the
particular design? ;-)
The point is that the managers in the latter category are not flooding the
app -- you have only a few of them hopefully, and can pay close attention on
the content. And normally they are helped out with more local RAII
sub-managers.
Many real systems work processing 'requests', serving events from outsude.
That have a well defined point of entry, that is fortunately a
function/stack frame too. And you can use that to manage all the stuff that
is local-to that request. When it ends, you can be sure the app's state is
as before. Or changed *only* as expected by the req itself.
The more problematic RL cases when request create a "session" to work with
and must carry state of that further. So you will have a session manager
upwards. Normally some of the further events will bring the session to its
final state where it can be disposed -- then certainly releasing all
associated resources. If driven by outside events, certainly only a proper
design can make sure it happens consistently.
The style is like:
- 'delete' is forbidden in "client" code. It is privilige
of the the few library classes that serve as managers.
Like auto_ptr.
and who holds the auto-ptr?
It's a stupid rule anyway. It doesn't work in practice.
oh, goody so I wasn't so far out in the out-field
Still is is not at all stupid, and it works perfetcly fine in my practice.
Possibly my separation of things to "client" and "library" (aka framework,
support, etc) is not clear and misleads judgement.
The most power of C++ comes from the fact it gives superior tools to create
a quasi-language in which you can most directly and expressively state the
real aim of the program. To em the client or application code is what is
written in that latter language. That definitely don't have place for
keyword delete.
The
real rule for memory management is not to use dynamic allocation
at all, except when the object lifetime is explicit (e.g. a call
in a telephone system).
if things are of variable size? Oh yes use std::vector (or other
suitable container).
Like in these very cases. Client code shall no way attempt to implement
containers, string, etc -- but use them for good. :) It asks dynamic
stuff in indirect way, like resize, or invoking factory calls.
Eliminating dynamic memory use is rare -- probably extinct outside embedded.
I'm beginning to form the rule of thumb that all uses of new[] are
errors. Or at least need looking at very hard.
Absolutely. delete[] was the first thing I blacklisted and it certainly
dragged new[]. And didn't meet a single case it was needed in the last 15
years.
[simple example] (too simple if you ask me)
think a program that processes text manipulating strings all
its time. A C++ solution would use std::string, (or any of
the much better string classes). Doing all the
passing-around, cutting, concatenating, etc.
consider a text editor that allows the user to delete text.
Who deletes the string that holds the deleted stuff and when[?]
That's probably not a good example. The text buffer holds the
text before it's deleted, and the deleted text itself is never a
separate object, unless...
sorry, "who deletes the text buffer?"
In a single-doc editor the text buffer can be permanent.
(I guess text editor is not a good forum example, the questions you rised
can be easily answered with a multitude of alternative designs, all with
pros&cons, yet trivially ensuring resource control -- but it would take a
book-worth amount of words. If really needed, pick some sub-part, or narrow
it down.)
What if the editor has Undo/Redo?
Then you save the deleted text in a redo record. Which will be
deleted when the requirements specifications says it should be
deleted.
but it's no longer solved by RAII alone
Undo also can be made with several approaches, like keeping snapshots of the
current state, or record request to play forward or back.
What you mean by "RAII alone"? Possibly we speak on distict tracks because
you fill that term by different content...
The meaning tied to original mosaic is actually rearly meant literally --
the most common use is RRID (res. release is destruction) leaving
allocation freeform -- still routinely referred as RAII. :-o
As I use it RAII stays for an even relaxed form, that allows the full
interface auto_ptr has, including reset() and release() -- just makes sure
dedicated ownership is maintained, and destruction of a manager does mean
release of anything owned.
Maybe we could call it Controlled Resource Management, or Controller-based
RM, or something like, but it would end up unused like RRID, and I did not
see in practice RAII's tendency to force restrictions.
Sure you can, and many of us do it in practice. C++ has
destructors that are called automaticly at well defined
points
the points are not always so well defined.
IMO discounting UB and other blacklisted cases they are.
sorry, the points at which delete are called is well defined with
RAII, it's just the point at which an "entity object" is released is
not specified by a FIFO protocol and hence RAII is not a complete
solution.
If you mean the "is-initialization" part, sure, it covers only a subset of
cases.
CallManager::process_event (std::auto_ptr<Event> event)
{
std::auto_ptr<Call> call(0);
if (is_new_call (event))
{
call = CallFactory::create_call (event);
CallList::add_call (call);
}
else
{
call = CallList::lookup_call (event);
}
call->process_event(event);
}
<<
This does not make much sense to me, id call_list is the manager/owner of
calls, you don't use auto_ptr like that, and especially don't place the
result of lookup in auto_ptr. There must be exactly one owner. Maybe you
meant shared_ptr, but it looks like overkill too.
And this is a good example what client code shall NOT do by any means --
leave call management with the call manager. So the whole thing becomes a
one-liner:
CallList::get_call(event).process_event(event);
get_call within the manager can see whether it is existing or a new call,
create as necessary and return one by ref. (This example implies no NULL
possibility, if there is one, make it
if(Call * pCall = CallList::get_call(event))
pCall->process_event(event);