ld a crit :
Please tell me specific instances of "buggy"...
Thanks
in list.c:
// line 18
typedef struct _list_element {
struct _list_element *Next;
char Data[MINIMUM_ARRAY_INDEX];
} list_element;
// line 63
result = MALLOC(li,sizeof(*result)+li->ElementSize);
// line 73
memcpy(&result->Data,data,li->ElementSize);
// line 348
static void * GetElement(List *l,size_t position)
{
....
return rvp->Data; // UNDEFINED BEHAVIOR
}
// line 488
static bool Equal(List *l1,List *l2)
{
CompareFunction fn;
....
fn = l1->Compare;
fn(link1->Data,link2->Data,&ci) // UNDEFINED BEHAVIOR
}
idem in IndexOf, lcompar+sort, Apply, GetFirst, GetNext, GetPrevious,
GetCurrent, and so on...
idem for all your containers (I haven't checked all)...
My guess is that you write code only on Intel platform and don't know
what memory alignment constraints means (aka "bus error" on Unixes).
In particular, your container store object by "value" and
No, because you can just store a POINTER to your data, instead of the
data. Yes, I store the objects by value, but that "value" can be just a
pointer, and in that case you have a store by reference.
the problem is that you use void pointers everywhere, so from the
user's point of view, it will very be hard to make the difference
between pointer to pointer, pointer to value, pointer to "internal"
data structures (e.g. list_element), etc...
What is nice in this interface is that the decision of storing by value
or by reference can be done by the end user of the library and not by
the library writer.
It would be also nice to
split the file container.h into separate headers for each container
and to avoid the use of reserved identifier (_[AZ]).
That can be done but I think it uses up too much memory (neuron memory
in the user's brain). You would have to remember that
#include <list.h>
#include <arraylist.h>
or it was
#include <lists.h>
#include <arraylists.h>
???
Please let's simplify.
- "Sub-classing" section has nothing to do with the term subclassing
in the literature.
Sub-classing as is understood in event-oriented programming (Windows
system for instance) means to put yourself at a specific point in a
call-chain.
If function a calls function B that calls function C in a call chain,
you replace the function pointer in the vtable of B, and instead of
calling C directly you do something and THEN call C, or you call
C and THEN you do something, or you do not call C at all.
this is called "method swizzling" and it's dangerous when applied on
an entire "class".
http://zathras.de/angelweb/blog-method-swizzling-considered-harmful.htm
As soon as you change something dynamically (possibly after instances
creation) in the behavior of a static type, you break the type system.
Precisely the ability of replacing those function pointers with your own
is one of the advantages of my approach.
do you have a useful application of what you propose?
Globals are thread safe if you understand that a shared ressource
needs semaphore protection before you change it.
Not only before you change it, but also *every time you read it*.
That's why it has a strong impact on how you access your vtable, and
therefore your interface. You can have a look to the history of errno
to see how threads introduced trouble to this global variable.
But this is so basic I
surely do not need to explain it to you.
This is far from being basic since it's part of one of the most active
research field in computer science (e.g. memory models, transactional
memory, etc...). But here again, I guess you have never used system
threads or only on Intel platforms with specific hardware support
(e.g. caches controllers support snooping and snarfing).
But maybe you mean something else? Please explain.
Done.
Why?
see above.
People that want to change a global resource can do it themselves.
Suppose I follow your advice. Then all changes (and each change) to the
tables provokes a copy.
not each changes, each time you want to create a "derived type"
That can be exactly what is needed in some
situation where you need to be able to go back and have the old
behavior, but could be just a waste if you do NOT want the old behavior
because you do not want to ever use it.
see above.
In the second case the wasted space is unavoidable if you have a
provided method.
Can you elaborate? I just do not understand what you mean here.
google for "method swizzling", "class posing", "meta-object protocol
or MOP", and related topics.
No. If you write:
Myobject->VTable->Add(Object, data);
instead of
iList.Add(Object,Data);
In the first case, the code REMAINS THE SAME if the object changes from
List to Array to whatever.
Not what so ever, your interfaces are not identical and the overuse of
void pointers make this kind of manipulation extremely error prone.
It is only in the second case that you need to change the code.
Using the FIRST syntax, even if it is more cumbersome, you have
container independence. Obviously the declaraion of "Object" must be
changed anyway.
That was my point, re-read my prose: "declaration of locals and
args" (just below).
No, see above.
It doesn't need improvement.
Ok. Go on, it's perfect.
Because I am not writing a full fledged framework for object oriented
programming in C but a container's library that should be extremely
small, stay within the language, and not invent a new way of doing OO
programming in C.
That was my proposal, see last line of my post. I don't talk to
rewrite OOC nor COS (from scratch, this would take you years).
YOU have done that. That is a DIFFERENT project.
Please explain how that would look like, specifically for, say, lists.
How would I call iList.Add(List,data);
in your proposal?
I would call it the same since this is a statically typed call
equivalent to iList_Add(List, data) (unless the vtable is read-write
which is not acceptable). I was talking about dynamic_cast, or
interface a-la-java or dictionary a-la-Haskell, which means how to
find a subset of the iList interface at runtime (after a static type
erasure) like comparable, serializable, iterable, etc... For example,
with an array of void pointers, you can do nothing safe, but with an
array of struct object* you can recover the original type of the
container (list) and all its implemented interfaces safely (or manage
properly the non-support for the interface you are looking for). This
will allow to write generic Apply for all containers which support the
interface iterable for example. Again all this in C requires only few
extra lines of code and some coding convention comparing to classical
ADT interfaces in headers.
Cheers,
Laurent.