Frank Silvermann wrote On 06/14/06 15:57,:
Eric Sosman wrote:
[...]
Also, when writing a function that our C++ friends
might call a "constructor," consider writing a companion
"destructor" function at the same time. In this case the
companion would simply call free(), but other uses may
eventually appear and you'll be prepared for them. For
example, you might decide that the program creates and
destroys so many of these things that it's worth while to
implement a little cache instead of thrashing back and
forth so much with malloc() and free(). This will be easy
to do if the "clients" call your destructor function, much
harder if they're in the habit of calling free() directly.
Even a question like "What is the maximum number of these
things the program ever uses at any one time?" is easy
to answer if you've followed the constructor/destructor
pattern, hard if you can't "see both ends" of a thing's
lifetime.
I see your point. Here I might put the output in a function:
void print_it_and_free_it{char * p, n)
There's a fairly important design principle known as
KISS for "Keep It Simple, Stupid!" Try to get a function
to do one easily-described job and do it well; avoid writing
functions that resemble Swiss Army pocket knives with fifty-
leven folding blades. Here, I'd suggest writing a print_it()
function and a separate free_it() function; the two tasks
(printing and freeing) don't seem to have much to do with
each other, and probably don't belong together. What if you
someday want to print it, adjust it, print it again, and
then free it? What if you want to free it without printing?
You can build more different kinds of things out of the simple
shape of a brick than from the complex shape of a fireplace.
(This advice isn't really C-specific or even programming-
specific; it applies in a lot of endeavors. If anything, C
offers less "active help" to the programmer than some other
languages do, so there's a higher premium on simplicity as
an aid to understanding -- and error avoidance ...)
I'm a little sketchier on what a cache would mean in this context. frank
The example you gave probably wouldn't have much use for
a cache. (But then, the example didn't seem all that realistic:
How frequently do you find yourself needing an array of the
integers 0 through n-1?) But suppose you're writing something
like a chess-playing game that allocates lots and lots of data
structures to represent possible game positions. After it
ponders matters for a while, your program chooses one of the
available moves and thereby discards all the positions that
resulted from moves not chosen. Then the opponent chooses a
move and once again your program discards a lot of data, and
then you start exploring the new situation and re-allocating
all those position records again. Instead of diving in and
out of malloc() and free() all the time, you might find it
advantageous to "free" a position by just sticking it on a
list of position records that are currently unused and "blank."
Then when you need a new position you can recycle one of the
old ones, and only use malloc() when the supply of recyclables
runs dry.
Anyhow, it's far from unlikely that you'll find reasons to
want to do something special when objects are deallocated,
even if those reasons aren't apparent from the start. If you've
written a free_it() function, even if it does nothing but call
free() when you first write it, you'll have given yourself a
convenient place to add the special sauce later on. But if your
callers just call free() behind your back, ...