How does that change what I said? It doesn't matter if it's allocated
and freed directly or via function calls. (Heck, malloc() and free() *are*
function calls.)
My point is that this manual construction/destruction requirement is
"inherited" by anything that wants to use those types. For example, if
you create a new struct that has such a type as a member, this struct
will now also have to be constructed/destructed manually, and so on.
The language offers no means to automatize and hide this in any way.
well, when you create the new type / structs and functions, you wrap the
old ones.
so, no big deal, one creates/destroys objects in a similar way,
regardless of how many levels are involved.
alternatively, one can treat them all as the root type by using function
pointers to handle the case of the destruction of subtypes:
struct FOO_SomeType_s {
void *data; //member
void (*deinit)(FOO_SomeType *self);
....
};
then:
void FOO_FreeSomeType(FOO_SomeType *self)
{
if(self->deinit)
self->deinit(self);
free(self);
}
in practice, it is all "not really a big deal".
for added safety though, one can use a garbage collector in place of
malloc/free, and give the GC the ability to call destructors, creating a
mechanism vaguely similar to Java's finalizer system.
Containers being homogeneous has nothing to do with genericness. A container
being generic means that you can use it with any type, rather than it being
fixed to a single hard-coded type, even if the container is homogeneous.
Even if the container is hard-coded for one single type, if that type
requires construction and destruction, it makes the implementation of the
container more complicated and more error-prone.
this seems like hair-splitting IMO.
only one of 2 possibilities exist:
the container is responsible for destroying its contents, in which case
it will do so;
it will not be responsible for doing so, and it will be the
responsibility of the client to destroy all container contents and then
destroy the container.
IMO probably one of the most common "containers" in C is the internally
linked-list (every member points to the next one) in which in the common
case destroying the container and destroying its contents are equivalent.
cur=first;
while(cur)
{
nxt=cur->next;
FOO_FreeFoo(cur);
cur=nxt;
}
Having to implement the same algorithms over and over has never been a
good programming practice. That's why generic programming is such a great
aid in this.
but, it is common practice, no different from a manual transmission, or
filling out and filing paperwork, ...
like "just do it", "doesn't matter if it is clean or elegant, just make
it work", ...
You see, these coding conventions exist solely because of the deficiencies
of the language, rather than because they are good programming practices.
With RAII they become unneeded, and the resulting code becomes simpler,
more straightforward and easier to read.
well, as noted, it is not C's job to be programmer friendly.
usually, it is used for things like implementing back-end libraries,
often with the front-end application-development done in a different
language, for example: C++, Java, C#, Ruby, Python, Lua, or some other
custom-designed language.
but, one may develop their libraries in C, since it may not be known in
advance which language may need to interface with the library code, and
in most ways C is the least common denominator (and for many types of
library, using C++ will not bring so much benefit internally if the
external API has to be C-like anyways).