C++ Internals: STL Vector, Part I
gahcep.com
gahcep.com
Does anyone know of other resources that describe the implementation details of higher level languages (Python, Ruby, or otherwise?). It is quite fun to learn what happens under the hood.
- As far as I know, it requires two calls to the allocator (one for the instance, and another for the resizable buffer).
- Even if allocated in the stack, unless you use a different allocator, the buffer is allocated in the heap.
If you do lots of operations per second, it makes the memory allocation functions, e.g. malloc(), suffer (depending on allocator algorithmic complexity, it could be a bottleneck).
P.S. an implementation of vector type for C, using dynamic size arrays, with only one allocation per container, using also geometric growing, and supporting both stack and heap allocation: https://github.com/faragon/libsrt/blob/master/src/svector.h
We see that in the API: functions return a new vector, which the caller must capture (and at the same time forget about the vector pointer that was passed in and all copies thereof, to avoid a use-after-free error).
That captured vector must be stored somewhere, such as in a stack variable or a member variable of an object.
That storage location itself must be allocated.
Hence, effectively, two allocations.
The memory management is manual: if you leave a scope without freeing all the "sv" vectors (by any means, including longjmp: your poor man's exception handling in C) you have a leak.
{
std::vector<int> stdvi; // stack for "stdvi" + heap for buffer
}
// heap object deallocated
{
sv_t *sv = // stack for "sv"
sv_alloc(sizeof int, 42); // heap for buffer
}
// heap object leaked
If a C++ programmer were to use this "sv", he or she would likely wrap it in an inline template class which manages the ownership of the buffer to prevent leaks and perhaps facilitate sharing. And that would just re-create std::vector basically. This template class would add next to zero overhead.Regarding identity, you can use double pointers.
In the case of willing to eliminate leakage risks (or speed-up the operation, avoiding calling the heap allocator), you can use stack allocation (using sv_alloca instead of sv_alloc), with implicit free, e.g.
{
sv_t *a = sv_alloca_t(SV_INT32, 42);
sv_t *b = sv_alloca_t(SV_INT32, 42);
sv_t *c = sv_alloca(sizeof(struct mystruct), 42);
struct mystruct { int r, s; }
y = { 1, 2 }, z = { 3, 4 };
sv_push_i(&a, 0);
sv_push_i(b, 1); sv_push_i(b, 2); sv_push_i(b, 3);
sv_cat(&a, b);
sv_cat(&a, a, b);
sv_push(&c, &y, &z, &y, &z, &y, &z);
sv_cat(&c, c);
/* implicit free */
}Also, please note that is a C library, so the only "RAII"-like implicit freeing is only possible using stack allocation (or compiler-specific stack cleanup callbacks). In my opinion is not worth it to encapsulate it for C++, as for C++ the STL library is already very good for most cases.
With heap allocation (you could mix both):
{
/* Using the heap, you can start with size 0,
* the vector will grow automatically:
*/
sv_t *a = sv_alloc_t(SV_INT32, 0);
sv_t *b = sv_alloc_t(SV_INT32, 0);
sv_t *c = sv_alloc(sizeof(struct mystruct), 0);
struct mystruct { int r, s; }
y = { 1, 2 }, z = { 3, 4 };
sv_push_i(&a, 0);
sv_push_i(b, 1); sv_push_i(b, 2); sv_push_i(b, 3);
sv_cat(&a, b);
sv_cat(&a, a, b);
sv_push(&c, &y, &z, &y, &z, &y, &z);
sv_cat(&c, c);
/* explicit free */
sv_free(&a, &b, &c);
}Not really, e.g. you can allocate with one allocation both: header + the buffer for the data. When growing, you grow with: size_of_header + elem_size * num_elems (if allocated in the heap).
If you use the stack allocation, you must allocate the maximum size you're going to use, e.g. for pre-allocating room for 100 elements you can do: sv_alloca(sizeof(struct mystruct), 100) or sv_alloca_t(SV_INT32, 100) for built-in type (built-in types for signed/unsigned ingegers allow mixing input types, passing them by value). In the case of stack allocation, if reaching the size limit via "push", you got error in return, but the vector is safe with the last successful operation.
What a horrible abuse of C++11 auto function declarations.
Is it idiomatic? No. Should it be used as an example for new learners of the language? Probably not. Should it be in an educational article? Probably not.
But one person't "abuse" is another person's "creativity." I love seeing how syntax can evolve beyond its intended uses.
My opinion - yes. I never imagined typing out type names was a hassle when using an IDE like Visual Studio. I know a number of people use it religiously - but when I read code I find it easier to read C++ with types than the auto keyword everywhere. But I'm sure a number of people still use vi (or emacs), and even smaller number of those people use gdb or some other debugger.
Apparently Linus doesn't use a debugger [1]. I have some comments on that - but I'll keep them to myself.
[1] http://www.linuxtoday.com/infrastructure/2000090700221OSCYKN
"Guideline: Remember that preferring auto variables is motivated primarily by correctness, performance, maintainability, and robustness—and only lastly about typing convenience." [1]
http://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-...
Which seems to be an often case in C++, class and class/typename comes to my mind. Also, old auto use was removed to the new set of meaning right?
What? I want whatever he is smoking.
> correctness
C++ allows for casting to/from different types (the core behind polymorphism) - but if you cast to the wrong type then that's your own fault. If you want a warm fuzzy feeling of not worrying about bad casts - use Python/Go/FreeBASIC/some other language.
> performance
That is complete and utter delusion. Your program won't run any faster or optimize better because you used the auto keyword [1]. If anything - it will take even longer (ms longer) to compile.
> maintainability
I have an odd feeling about that. Yeah you can change the return type of a method or function and you don't have to worry about changing the code calling those methods/functions. But if you are changing to a vastly different type - you are going to have to change the calling code anyways.
> robustness
I don't want the compiler to fix my mistakes. I want the compiler to tell me how stupid I am and force me to fix them.
[1] http://stackoverflow.com/questions/19618759/c-11-auto-compil...
I find it easier to read now, specially when using iterators on for loops.
auto now = boost::chrono::steady_clock::now().time_since_epoch();
auto timeSinceEpoch = boost::chrono::duration_cast<boost::chrono::milliseconds>(now).count();
(This is from http://stackoverflow.com/questions/16700277/get-a-double-fro...).
The person writing the answer is dilligent and hence has supplied the types as a preamble. Most articles / answers / api docs dont take the effort to elaborate on return value types.
Isn't it what realloc is for?
P.S. you can find a human-readable "reserve"/"grow" (with geometric heuristic grow)/"shrink_to_fit" vector implementation for C (similar idea to the C++ counterpart, except for using just one allocation, instead of 2 -one for the instance, and another for the buffer-): https://github.com/faragon/libsrt/blob/master/src/sdata.c