Quantcast
Viewing all articles
Browse latest Browse all 42

Answer by Lundin for Generic Vector implemented in C language

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 and finish.

    Commonly used names for the constructor are init, create, construct, alloc etc.
    Commonly used names for the destructor is delete, 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 use void*, 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 for uint8_t*.

  • You should implement some manner of error handling through a return type enum or similar, rather than using assert.

  • 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 to const*. 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 a void* anyway?
  • --(vect->length); looks strange, just write vect->length--;.
  • Casting the result of malloc/realloc is pretty pointless and just adds clutter. In particularly, casting from void* to void* is very pointless.

Viewing all articles
Browse latest Browse all 42

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>