Design:
Overall, function naming and const correctness etc looks good. The header is nicely formatted and easy to read. Most function names are self-explanatory, though I wouldn't use
start
andfinish
.Commonly used names for the constructor are
init
,create
,construct
,alloc
etc.
Commonly used names for the destructor isdelete
,destruct
,free
,cleanup
etc.I'm getting some déjà vu from this whole vector implementation - check out this review regarding how you could implement the concept of opaque type.
It's strange and needlessly complicated to use
void**
for the data. Why can't you usevoid*
, since all data stored can be assumed to be of the same type?void*
is overall not very meaningful when dealing with raw data, so you should consider swapping it foruint8_t*
.You should implement some manner of error handling through a return type
enum
or similar, rather than usingassert
.Generally, when developing actual library quality data containers, you should avoid excessive calls to malloc & realloc. These are very slow functions and calling them often also causes heap fragmentation and poor cache use etc. It is therefore custom to allocate a "x times alignment" capacity per vector instance, regardless of how much memory the user actually needs. There's plenty of heap memory, execution speed is much more important than a few bytes of RAM here and there.
Example: Lets say you have a 32 bit CPU = 4 byte word alignment. The user wants to store 10 bytes of data. Regardless of that, you allocate a minimum of 4 * 8 = 32 bytes and keep track of how much of that memory that's used. 4 = alignment and 8 = some conventient multiple of 8. Then when the user requests a resize to 20 bytes, you simply mark 20 out of your 32 allocated bytes as used. Only when they go beyond the allocated size do you call malloc. But this time you allocate a total of 4 * 8*2 = 64 bytes. Next time 128 bytes, and so on. This is to reduce the amount of calls to malloc functions. And there is usually no need to shrink the allocated space, ever.
The above is especially important for "
vector::push_back
"-like APIs, where you add one item at a time! You can't call malloc each time that happens, or you would have been better off using a linked list.With the above in mind, capacity should be handled internally by your code, not by the user!
capacity
should be a private variable and the caller shouldn't meddle with it.
Bugs:
- You don't check if
realloc
succeeded.
Style:
- It's a bit subjective, but in my opinion
const * const
function parameters are just clutter and should be changed toconst*
. The parameter is a local copy of the original pointer and the caller really couldn't care less if the function modifies that pointer internally. What meaningful change could a function do to a copy of avoid*
anyway? --(vect->length);
looks strange, just writevect->length--;
.- Casting the result of malloc/realloc is pretty pointless and just adds clutter. In particularly, casting from
void*
tovoid*
is very pointless.