Richard Heathfield a écrit :
> For the list to own the data (which, personally, I think is a good
idea), it is going to have to make its own copy if it is to persist
past the point where the feed data goes out of scope. That means a
malloc, obviously (or some dark equivalent thereof), and some people
will object purely on those grounds. That's your first major problem
- some people will want the list to own the data, and others will
very much want it *not* to own the data.
This is easily solved. If you do not want the list to own the data
you do not give any data to the list. You make a list of void pointers
to your data. Then, you own the data and no copies are done.
Assuming it does own the data, you then have the problem of pointers
within that data.
Shallow copy since the list container can't know what are pointers
and what are just integers.
Do you do a deep copy, or a shallow copy? If you do
a shallow copy, you risk ownership confusion and of course dangling
pointers. If you do a deep copy, you will probably need the user to
provide copying semantics. This complicates the interface.
Exactly. What is important here is that we keep it simple. Not
ALL possible uses MUST be covered. Just 90% of all uses. Why
should the list container care about what it has to copy?
Instead of passing a pointer to a copy-constructor the user
of the list is responsible for copying and THEN passing that to the
list. Good design goes through keeping tasks separated and
responsibilities clear. The container cares about its data elements,
but it doesn't know ANYTHING about them.
PERIOD. End of story.
One way to obviate this problem is to have a sort of registration
process when setting up a new list, so that all the complication is
up front.
Not necessarily. Comparison defaults to memcmp. Destructors are not
provided but you can use an iterator to iterate all elements of the list
when it is destroyed. This is not C++, it is C. The compiler does
much less for you, but the language is easier to learn and use.
You could also use the opportunity to register destructors,
comparators, and iterators. For example:
List *list = newList(sizeof Data,
MyTypeCopyConstructor,
MyTypeDestructor,
NULL, /* we happen not to need a comparator */
MyTypeIterator);
Copy constructors defaults to memcpy. If you want a fancy one
you can have it but it would add overhead.
A copy constructor would be of type int(const void *, void *);
I assume this means
int (*copyconstructor)(const void *,void *);
Note that you use copy(source , destination) what is the contrary of
copy functions in C. (memcpy strcpy). I would rather have it the
other way. I do not think that this is needed though. The user
copies the data because the user knows what the data is storing,
where are the pointers what do they point to, etc.
Division of reponbsabilities. Again, this is VERY important in
software design.
A destructor would be of type int(void *, void *);
I do not see why you need a destructor *in the list*. If you want to
destroy an item to the list, you call GetElement, you do whatever you
need to do when the item is destroyed, and then you call Delete
with the index of the item.
Yes, it is not as "nice" but it is just as good.
A comparator would be of type int(const void *, const void *, void *);
OK. You need that. It defaults to memcmp but you should be able to
change it. By the way, what is the third argument?
An iterator would be of type int(void *, void *);
Wait. It is much simpler to pass the callback at each call to
the iterator.
In each case, the last (optional - NULL is okay) pointer is to an
arbitrary structure that can carry or even accept side channel
information.
Ahh OK. That's clever. I will add it to my implementation.
These function pointers (the non-NULL ones, anyway) would then be
called whenever the standard data structure is called upon to do
whatever. For example, list->iterate(&myvars) would call
*MyTypeIterator for each item in the list, handing it a pointer to
the current item and the pointer we passed to it.
Correct. I do that, but I receive the function pointer at each
call to the iteration function to avoid storing it in the
list and increasing the fixed overhead. You are NOT going to
iterate all lists, only some of them. Then, it is more
space efficient to pass the iterator to the iterate list
function.
You would also need to have functions to allow these defaults to be
updated - e.g. list->SetNewIterator(MyTypeAnotherIterator).
Not if you pass it at each call.
(This is pretty much how my own library works, by the way.)
The problem is that it's all getting very complicated, and that in
itself is very likely going to make it unacceptable to a standards
committee.
Complicated???
Too complicated???
This is NOTHING. I am sure you have designed software that is MUCH MORE
complicated than this stuff!
And most of us have. Including committee members.
But every way of simplifying it will lead either to complications in
other areas or insufficient power and flexibility.
What is important to have clear at the start is that it is not
possible to achieve PERFECTION. Each user will have some
special needs. Since all those function pointers are writable,
users can add or eliminate features as they wish, always MAINTAINIG
the same interface!
Bottom line: there are a great many ways to do this, and every way has
its advocates and detractors. Getting a consensus within ISO for
adopting any single proposal is a Sisyphean task.
I would not aim so high right now. I would aim to develop here in this
group a consensus implementation that is widely distributed and
with no strings attached at all.
After it has spread around, we have experience with it, it becomes
CURRENT PRACTICE!
and ISO will gladly adopt it.
Thanks for paricipating