My question is basically when to choose QVector and when to choose QList as your Qt container. What I already know:
Qt docs: QList class
For most purposes, QList is the right class to use. Its index-based API is more convenient than QLinkedList's iterator-based API, and it is usually faster than QVector because of the way it stores its items in memory. It also expands to less code in your executable.
The same is written is this very popular Q&A: QVector vs QList. It also favors QList.
But: on recent Qt World Summit 2015 KDAB presented "Why QList is harmful", this is basically here:
QList considered harmful
Don't use QList, use Q_DECLARE_TYPEINFO
As far as I understand the idea is that QList for almost all types is inefficient when allocating new elements in heap. Each time you are adding new element, it calls new (once per element) and this is inefficient compared to QVector.
This is why now I am trying to understand: is it QVector which we should choose as default container?
Qt advertises QList as the "jack of all trades", but the other half of that saying is "master of none". I'd say QList is a good candidate if you plan on appending to both ends of the list, and those are no larger than than a pointer, as QList reserves space before and after. That's about it, I mean as far as good reasons to use QList are concerned.
QList will automatically store "large" objects as pointer and allocate the objects on the heap, which may be considered a good thing if you are a baby, which doesn't know how to declare a QVector<T*> and use dynamic allocation. This is not necessarily a good thing, and in some cases it will only bloat the memory usage and add extra indirection. IMO it is always a good idea to be explicit about what you want, whether it is pointers or instances. Even if you do want heap allocation, it is always better to allocate it yourself and simply add the pointer to the list than construct the object once, then have have copy construct that on the heap.
Qt will return you a QList in a lot of places where it comes with overhead, for example when getting a QObject's children or you search for children. In this case it doesn't make sense to use a container that allocates space before the first element, as it is a list of objects which are already there, not something you are likely to prepend to. I also don't much like the absence of a resize() method.
Imagine a situation where you have an object with size of 9 bytes and byte alignment on a 64 bit system. It is "far too much" for QList so instead it will use 8 byte pointer + CPU overhead for the slow heap allocation + memory overhead for the heap allocation. It will use twice the memory and with an extra indirection for access it will hardly offer performance advantages as advertised.
As of why QVector cannot suddenly become the "default" container - you don't change horses mid-race - it is a legacy thing, Qt being such an old framework, and even though a lot of stuff has been deprecated, making changes to widely used defaults is not always possible, not without breaking a lot of code, or producing undesired behavior. Good or bad, QList will likely continue being the default all the way throughout Qt 5, and likely in the next major release as well. The same reason Qt will continue using "dumb" pointers, for years after smart pointers have become a must and everybody is crying about how bad plain pointers are and how they should not be used ever.
That being said, nobody is forcing you to use QList in your design. There is no reason why QVector should not be your default container. I myself don't use QList anywhere, and in the Qt functions which return a QList I merely use as a temporary to move stuff into a QVector.
Furthermore, and this is only my personal opinion, but I do find a lot of design decisions in Qt that don't necessary make sense, be that performance or memory use efficiency or ease of use wise, and overall there are a a lot of frameworks and languages which like promoting their ways of doing things, not because it is the best way to do it, but because it is their way to do it.
Last but not least:
For most purposes, QList is the right class to use.
It really boils down to how you understand this. IMO in this context, "the right" does not stand for "the best" or "the optimal", but for "good enough" as in "it will do, even if not the best". Especially if you know nothing about different container classes and how they work.
For most purposes, QList will do.
To sum things up:
QList PROs
you intend to prepend objects no larger than the size of a pointer, since it reserves some space in the front
you intend to insert in the middle of the list objects (substantially) larger than a pointer (and I am being generous here, since you can easily use QVector with explicit pointers to achieve the same and cheaper - no extra copy), since when resizing the list, no objects will be moved, only pointers
QList CONs
doesn't have a resize() method, reserve() is a subtle trap, since it will not increase the valid list size, even if index access works it falls in the UB category, also you will not be able to iterate that list
does an extra copy and heap allocating when object is larger than a pointer, which might also be an issue if object identity matters
uses extra indirection to access objects larger than a pointer
has CPU time and memory usage overheads due to the last two, also less cache friendly
comes with additional overhead when used as a "search" return value, since you are not likely to prepend or even append to that
only makes sense if index access is a must, for optimal prepend and insert performance a linked list might be a better option.
The CON's marginally outweigh the PROs, meaning that while in "casual" use QList might be acceptable, you definitely don't want to use it in situations where CPU time and/or memory usage are a critical factor. All in all, QList is best suited for lazy and careless use, when you don't want to make the consideration of optimal storage container for the use case, which would typically be a QVector<T>, a QVector<T*> or a QLinkedList (and I exclude "STL" containers, since we are talking Qt here, Qt containers are just as portable, sometimes faster, and most certainly easier and cleaner to use, whereas std containers are needlessly verbose).
In Qt 5.7, the documentation was changed concerning the topic discussed here. In QVector it is now stated:
QVector should be your default first choice. QVector<T> will usually give better performance than QList<T>, because QVector<T> always stores its items sequentially in memory, where QList<T> will allocate its items on the heap unless sizeof(T) <= sizeof(void*) and T has been declared to be either a Q_MOVABLE_TYPE or a Q_PRIMITIVE_TYPE using Q_DECLARE_TYPEINFO.
They refer to this article by Marc Mutz.
So the official point of view has changed.
QList is an array of void*.
In its normal operation, it news the elements on the heap and stores a pointer to them in the void* array. Like a linked list, that means that references (but, unlike linked lists, not iterators!) to elements contained in the list remain valid under all container modifications until the element is removed from the container again. Thus the name "list". This datastructure is called an array-list and is used in a lot of programming languages where every object is of reference type (say, Java). It is a very cache-unfriendly data structure, like all node-based containers.
But the resizing of the array-list can be factored into a type-independent helper class (QListData), which is supposed to save some executable code size. In my experiments, it's next to impossible to predict which of QList, QVector or std::vector produces the least executable code.
This would have been a good data type for the many Qt reference-like types such as QString, QByteArray, etc., which consist of nothing more than a pimpl pointer. For these types, QList gained an important optimisation: when the type is not larger than a pointer (and please note that this definition depends on the platform's pointer size - 32 or 64bits), instead of heap-allocating objects, the objects are stored in the void* slots directly.
This is only possible, though, if the type is trivially relocatable. That means it can be relocated in memory using memcpy. Relocation here means I take an object, memcpy it to another address and - crucially - not run the destructor of the old object.
And this is where things started to go wrong. Because unlike in Java, in C++ a reference to an object is its address. And while in the original QList, references were stable until the object was removed from the collection again, by putting them into the void* array this property no longer holds. This is no longer a "list" for all intents and purposes.
Things continued to go wrong, though, because they allowed types that are strictly smaller than a void* to be placed in a QList, too. But the memory management code expects elements of pointer size, so QList adds padding(!). That means that a QList<bool> on 64bit platforms looks like this:
[ | | | | | | | [ | | | | | | | [ ...
[b| padding [b| padding [b...
Instead of fitting 64 bools into a cache line, like QVector does, QList only manages 8.
Things went wrong out of any proportion when the docs started calling QList a good default container. It's not. The original STL states:
Vector is the simplest of the STL container classes, and in many cases the most efficient.
Scott Meyer's Effective STL has several items that start with "Prefer std::vector over...".
What is true in general C++ is not suddenly wrong just because you're using Qt.
Qt 6 will fix that particular design mistake. In the meantime, use QVector or std::vector.
If the size of the QList's element type is greater than the pointer's
size QList performs better than QVector because it doesn't store the
objects sequentially but stores sequentially pointers to heap copies.
I'd tend to say the opposite. It'll be much worse off, when going through the items.
If it stores it as pointers on the heap won't QList be much worse off than QVector? The reason that sequential storage(QVector all the time) is so good is, that is is cache friendly, once you store pointers,you lose the data locality, start getting cache misses and it's horrible for performance.
The "default" container IMHO should be a QVector (or std::vector), if you're worried about lots of reallocation, then preallocate a reasonable amount, pay the once off cost and you'll benefit in the long run.
Use the *Vector by default, if you get performance problems, profile and change as necessary.
Please note that this has completely changed in Qt6:
https://www.qt.io/blog/qlist-changes-in-qt-6
QVector and QList are unified and the model of QVector is used as the underlying implementation. This means that Qt 5 QList's extra level of indirection for generic types is now gone and elements are always directly stored in the allocated memory. QList is the real class, with implementation, while QVector is just an alias to QList. QList in Qt 6 supports optimised prepend. It may now shrink on elements removal without usage of reserve. And the size limit of 2GB is removed.
QList is the best possible container to use generally as the documentation states. If the size of the elements' type is <= of the pointer's size = machine & OS bitness = 4 or 8 bytes then the objects are stored the same way as QVector does - sequentially in memory. If the size of the QList's element type is greater than the pointer's size QList performs better than QVector because it doesn't store the objects sequentially but stores sequentially pointers to heap copies.
In the 32-bit case the picture is as follows:
sizeof( T ) <= sizeof( void* )
=====
QList< T > = [1][1][1][1][1]
or
[2][2][2][2][2]
or
[3][3][3][3][3]
or
[4][4][4][4][4] = new T[];
sizeof( T ) > sizeof( void* )
=====
QList< T > = [4][4][4][4][4] = new T*[]; // 4 = pointer's size
| | ... |
new T new T new T
In case you want your objects to be laid out sequentially in memory no matter the size of their elements, as it is usually the case with OpenGL programming, then you should use QVector.
Here is a detailed description of the QList's internals.
Imagine, that we have DataType class.
QVector - array of objects, such as:
// QVector<DataType> internal structure
DataType* pArray = new DataType[100];
QList - array of pointers to objects, such as:
// QList<DataType> internal structure
DataType** pPointersArray = new DataType*[100];
Therefore, direct access by index will be faster for QVector:
{
// ...
cout << pArray[index]; //fast
cout << *pPointersArray[index]; //slow, need additional operation for dereferencing
// ...
}
But swaping will be faster for QList, if sizeof(DataType) > sizeof(DataType*):
{
// QVector swaping
DataType copy = pArray[index];
pArray[index] = pArray[index + 1];
pArray[index + 1] = copy; // copy object
// QList swaping
DataType* pCopy = pPointersArray [index];
pPointersArray[index] = pPointersArray [index + 1];
pPointersArray[index + 1] = pCopy; // copy pointer
// ...
}
So, if you need direct access without swaping operations between elements (such as sorting, for example), or sizeof(DataType) <= sizeof(DataType*), your better way is use QVector. In other case use QList.
QList behaves differently depending on what's inside (see source code struct MemoryLayout):
if sizeof T == sizeof void* and T is defined Q_MOVABLE_TYPE, then QList<T> behaves exactly like QVector, that is, the data is stored contiguously in memory.
if sizeof T < sizeof void* and T is defined Q_MOVABLE_TYPE, then QList<T> pads each entry to sizeof void*, and loses layout-compatibility with QVector.
in all other cases, QList<T> is a linked list and therefore slow to some degree.
This behavior is what makes QList<T> pretty much always a bad choice, because depending on nifty details, QList<T> is either really a list, or a vector. That's bad API design and prone to errors. (For instance, you will run into bugs if you have a library with a public interface that uses a QList<MyType> internally and in its public interface. sizeof MyType is < sizeof void*, but say you forgot to declare MyType as Q_MOVABLE_TYPE. Later, you want to add Q_MOVABLE_TYPE. This is binary incompatible, meaning that you now have to recompile all code that uses your library, as the memory layout of QList<MyType> changed in the public API. If you are not careful, you will miss this and introduce a bug. This illustrates quite nicely why QList is a bad choice here.)
That said, QList is still not entirely bad: It will probably do what you want most of the cases, but maybe it will do the job behind the scenes differently to what you might expect.
Rule of thumb is:
Instead of QList, use QVector<T> or QVector<T*>, since it explicitly says what you want. You can combine that with std::unique_ptr.
In C++11 and onwards, it is even considered best to just use std::vector, since it will behave correctly in a range-based for loop. (QVector and QList may detach and therefore perform a deep-copy).
You can find all these details and more in a presentation from Marc Mutz and in the video by Olivier Goffart.
Related
I used many programming languages that had something like .push() or .append() to add an object to the end of a dynamic array. Now, I learnt some C++ and noticed that it does not support that, but I instead have to implement a function that manually does this, and this function creates a new temporary array that is one element longer, loops through the old array and copies each element, adds the new object, deletes the old array, and copies the temporary array to the old array. Does the languages I already worked with that allow functions like .push() do the same thing at the assembly level?
In C++, we also have constructs that do their own memory management: std::vector<> comes to mind. It is advisable to be used over plain arrays when appropriate. std::vector<> maintains a buffer and resizes + moves only when necessary (i.e., when the buffer is too small to hold the newly inserted element); then it expands usually to double or similarly.
In other languages, you also need to implement some kind of memory management. However, it's not trivial whether this kind of resizing is necessary: for most scripting languages are actually implemented in a lower-level language, very usually C/C++/C--. So it's possible that std::vector<> (or even std::map<>) is used in the background. However, at the end of the day, someone must implement resizing 1, as memory is usually viewed as a one-dimensional array from which you allocate; you can't necessarily extend an allocation infinitely.
1 Note: theoretically, on some architectures it'd be possible to delegate this to virtual memory management and use segment registers for pointers, index registers for offsets, and let paging associate segment:index to physical addresses; however, that's rarely used nowadays.
Yes they do. For example python list (which are arrays, not lists) is doing exactly the same.
Note: You do not have to implement this yourself. That's what the STL is for in C++ and the relevant data structure for a dynamic array is std::vector. Except std::vector is smarter about growing the array, growing it by more than one element so not every push has to copy all the previous data. In fact it has ammortised cost of O(1) per push. If you know how much data you have it's still best to reserve the right amount of space from the start though.
A little bit of background first (skip ahead to the boldface if you're bored by this).
I'm trying to glue two pieces of code together. One is a JSON/YML library that makes heavy use of a custom string view object, the other is a piece of code from the early 2000s.
I've been seeing weird behavior for a long time, until I have traced it down to a memory issue, namely that the string views I construct in the JSON/YML library take a const char* as a constructor, and assume that the memory location of that char array stays constant over the lifetime of the string view. However, some of the std::string objects on which I construct these views are temporary, so that's just not true and the string view ends up pointing at garbage.
Now, I thought I was being smart and constructed a cache in the form of a std::vector that would hold all the temporary strings, I would construct the string views on these and only clear the cache in the end - easy.
However, I was still seeing garbled strings every now and then, until I found the reason: sometimes, when pushing things to the vector beyond the preallocated size, the vector would be moved to a different memory location, invalidating all the string views. For now, I've settled on preallocating a cache size that is large enough to avoid any conceivable moving of the vector, but I can see this causing severe and untracable problems in the future for very large runs. So here's my question:
How can I construct a std::vector<std::string> or any other string container that either avoids being moved in memory alltogether, or at least throws an error message if that happens?
Of course, if you feel that I'm going about this whole issue in the wrong way fundamentally, please also let me know how I should deal with this issue instead.
If you're interested, the two pieces of code in question are RapidYAML and the CERN Statistics Library ROOT.
My answer from a similar question: Any way to update pointer/reference value when vector changes capability?
If you store objects in your vector as std::unique_ptr or std::shared_ptr, you can get an observing pointer to the underlying object with std::unique_ptr::get() (or a reference if you dereference the smart pointer). This way, even though the memory location of the smart pointer changes upon resizing, the observing pointer points to the same object and thus the same memory location.
[...] sometimes, when pushing things to the vector beyond the preallocated size, the vector would be moved to a different memory location, invalidating all the string views.
The reason is that std::vector is required to store its data contiguously in memory. So, if you exceed the maximum capacity of the vector when adding an element, it will allocate a new space in memory (big enough this time) and move all the data here.
What you are subject to is called iterator invalidation.
How can I construct a std::vector or any other string container that either avoids being moved in memory alltogether, or at least throws an error message if that happens?
You have at least 3 easy solutions:
If your cache size is supposed to be fixed and is known at compile-time, I would advise you to use std::array instead.
If your cache size is supposed to be fixed but not necessarily known at compile-time, I would advise you to reserve() the required capacity of your std::vector so that you will have the guarantee that it will big enough to not need to be reallocated.
If your cache size may change, I would advise you to use std::list instead. It is implemented as a (usually doubly) linked list. It will guarantee that the elements will not be relocated in memory.
But since they are not stored contiguously in memory, you'll lose the ability to have direct access to any element (i.e. you'll need to iterate over the list in order to find an element).
Of course there probably are other solutions (I do not claim this answer to be exhaustive) but these solutions will allow you to almost not change your code (only the container) and protect your string views to be invalidated.
Perhaps use an std::list. Its accessing method is slower (at least when iterating) but memory location is constant. Reason for both is that it does not use contiguous memory.
Alternatively create a wrapper that wraps a pointer to a string that has been created with "new". That address will also be constant. EDIT: Somehow I managed to miss that what I've just described is pretty much a smartpointer minus automated deletion ;)
Well sadly it is impossible to be able to grow a vector while being sure the content will stay at the same place on classical OS at least.
There is the function realloc that tries to keep the same place, but as you can read on the documentation, there is no guarantee to that, only the os will decide.
To solution your problem, you need the concept of a pool, a pool of string here, that handle the life time of your strings.
You may get away with a simple std::list of string, but it will lead to bad data aliasing and a lot of independent allocations bad to your performances. These will also be the problems with smart pointers.
So if you care about performances, how you may implement it in your case may be not far from your current implementation in my opinion. Because you cannot resize the vector, you should prefer an std::array of a fixed size that you decide at compile time. Then, whenever you need it, you can create a new one to expand your memory capacity. This may be easily implemented by a std::list<std::array> typically.
I don't know if it applies here, but you must be careful if your application can create any number of string during its execution as it may induce an ever growing memory pool, and maybe finally memory problems. To fix that you may insure that the strings you don't use anymore can be reused or freed. Sadly I cannot help you too much here, as these rules will depend on your application.
How can I decide when choosing between std::containers (std::vector or std::array) and smart pointers pointing to arrays
I know containers are objects for memory managment. They are exception safe and there will not be any memory leak and they also provide veriuty of functions for memory managment(push.back etc) and smart pointers are pointer that also do not leak memory because they delete themsefs when they are not needed anymore(like unique_ptr when geting out of scope). Propably in containers there is an overhead every time they are created.
My question is how can i decide which method to use and why.
std::vector <unsigned char>myArray(3 * outputImageHight * outputImageWidth);
std::unique_ptr<unsigned char[]>myArray(new unsigned char[3 * outputImageHight * outputImageWidth]);
I would use the vector. Your pointer version offers basically no improvements over the vector, and you lose a lot of useful functionality. You're most likely going to need to measure the size and iterate your array at some point, with a vector you get this for free, whereas you'd need to implement it yourself for your pointer version; at which point you may as well have just used the vector to begin with.
There may be a performance cost instantiating the vector, but I doubt that it would be a bottleneck for most applications. If you're creating so many vectors that instantiating them is costing you time, you can probably be smarter about managing them (pooling your memory, custom vector allocators, etc). If in doubt, measure.
One example where you might need to use the unique_ptr<> version might be if you're working with a library written in C where you lose ownership of the array. For example:
std::unique_ptr<unsigned char[]>myArray(
new unsigned char[3 * outputImageHight * outputImageWidth]);
my_c_lib_data_t cLibData;
int result = my_c_lib_set_image(cLibData, myArray);
if (MYLIB_SUCCESS == result)
// mylib successfully took ownership of the char array, so release the pointer.
myArray.release();
If you have the choice though, prefer to use C++ style containers where you can.
std::vector, primarily because it better represents the "sequence of items in contiguous memory", it is the default representation for that, and enables a wide range of common operations.
vector has move semantics, so the benefit of std::unique_ptr is limited.
If you are lucky, your STL implementation `provides small vector optimization, skipping the memory allocation for small sizes.
-- edit: I wasn't aware SBO is apparently prohibited by the standard - sorry for getting your hopes up, thanks #KarlNicholl
If pointer semantics are required, a unique_ptr<vector<T>> or shared_ptr<vector<T>> is a valid choice with little overhead.
boost did introduce shared_array etc., that represent your second option better,
but I haven't seen them get much traction.
Always use STL containers except in situation where you have good reason to use pointers. Reasons are reliability and readability, IMO.
When storing objects in standard collections, what considerations should one think of when deciding between storing values vs pointers? Where the owner of these collections (ObjectOwner) is allocated on the heap. For small structs/objects I've been storing values, while for large objects I've been storing pointers. My reasoning for this was that when standard containers are resized, their contents are copied (small copy ok, big copy bad). Any other things to keep in mind here?
class ObjectOwner
{
public:
SmallObject& getSmallObject(int smallObjectId);
HugeObject* getHugeObject(int hugeObjectId);
private:
std::map<int, SmallObject> mSmallObjectMap;
std::map<int, HugeObject *> mHugeObjectMap;
};
Edit:
an example of the above for more context:
Create/Delete items stored in std::map relatively infrequently ( a few times per second)
Get from std::map frequently (once per 10 milliseconds)
small object: < 32 bytes
huge object: > 1024 bytes
I would store object by value unless I need it through pointer. Possible reasons:
I need to store object hierarchy in a container (to avoid slicing)
I need shared ownership
There are possibly other reasons, but reasoning by size is only valid for some containers (std::vector for example) and even there you can make object moving cost minimal (reserve enough room in advance for example). You example for object size with std::map does not make any sense as std::map does not relocate objects when growing.
Note: return type of a method should not reflect the way you store it in a container, but rather it should be based on method semantics, ie what you would do if object is not found.
Only your profiler knows the answer for your SPECIFIC case; trying to use pointers rather than objects is a reliable way of minimising the amount you copy when a copy must be done (be it a resize of a vector or a copy of the whole container); but sometimes you WANT that copy because it's a snapshot for a thread inside a mutex, and it's faster to copy the container than to hold the mutex and deal with the data.
Some objects might not be possible to keep in any way other than pointer because they're not copyable.
Any performance gain by using container of pointer could be offset by costs of having to write more copy code, or repeated calls to new().
There's not a one answer fits all, and before you worry about the performance here you should establish where the performance problems really are. (Just repeating the point - use a profiler!)
As someone with a lot of assembler language experience and old habits to lose, I recently did a project in C++ using a lot of the features that c++03 and c++11 have to offer (mostly the container classes, including some from Boost). It was surprisingly easy - and I tried wherever I could to favor simplicity over premature optimization. As we move into code review and performance testing I'm sure some of the old hands will have aneurisms at not seeing exactly how every byte is manipulated, so I want to have some advance ammunition.
I defined a class whose instance members contain several vectors and maps. Not "pointers to" vectors and maps. And I realized that I haven't got the slightest idea how much contiguous space my objects take up, or what the performance implications might be for frequently clearing and re-populating these containers.
What does such an object look like, once instantiated?
Formally, there aren't any constraints on the implementation
other than those specified in the standard, with regards to
interface and complexity. Practically, most, if not all
implementations derive from the same code base, and are fairly
similar.
The basic implementation of vector is three pointers. The
actual memory for the objects in the vector is dynamically
allocated. Depending on how the vector was "grown", the dynamic
area may contain extra memory; the three pointers point to the
start of the memory, the byte after the last byte currently
used, and the byte after the last byte allocated. Perhaps the
most significant aspect of the implementation is that it
separates allocation and initialization: the vector will, in
many cases, allocate more memory than is needed, without
constructing objects in it, and will only construct the objects
when needed. In addition, when you remove objects, or clear the
vector, it will not free the memory; it will only destruct the
objects, and will change the pointer to the end of the used
memory to reflect this. Later, when you insert objects, no
allocation will be needed.
When you add objects beyond the amount of allocated space,
vector will allocate a new, larger area; copy the objects into
it, then destruct the objects in the old space, and delete it.
Because of the complexity constrains, vector must grow the area
exponentially, by multiplying the size by some fixed constant
(1.5 and 2 are the most common factors), rather than by
incrementing it by some fixed amount. The result is that if you
grow the vector from empty using push_back, there will not be
too many reallocations and copies; another result is that if you
grow the vector from empty, it can end up using almost twice as
much memory as necessary. These issues can be avoided if you
preallocate using std::vector<>::reserve().
As for map, the complexity constraints and the fact that it must
be ordered mean that some sort of balanced tree must be used.
In all of the implementations I know, this is a classical
red-black tree: each entry is allocated separately, in a node
which contains two or three pointers, plus maybe a boolean, in
addition to the data.
I might add that the above applies to the optimized versions of
the containers. The usual implementations, when not optimized,
will add additional pointers to link all iterators to the
container, so that they can be marked when the container does
something which would invalidate them, and so that they can do
bounds checking.
Finally: these classes are templates, so in practice, you have
access to the sources, and can look at them. (Issues like
exception safety sometimes make the implementations less
straight forward than we might like, but the implementations
with g++ or VC++ aren't really that hard to understand.)
A map is a binary tree (of some variety, I believe it's customarily a Red-Black tree), so the map itself probably only contains a pointer and some housekeeping data (such as the number of elements).
As with any other binary tree, each node will then contain two or three pointers (two for "left & right" nodes, and perhaps one to the previous node above to avoid having to traverse the whole tree to find where the previous node(s) are).
In general, vector shouldn't be noticeably slower than a regular array, and certainly no worse than your own implementation of a variable size array using pointers.
A vector is a wrapper for an array. The vector class contains a pointer to a contiguous block of memory and knows its size somehow. When you clear a vector, it usually retains its old buffer (implementation-dependent) so that the next time you reuse it, there are less allocations. If you resize a vector above its current buffer size, it will have to allocate a new one. Reusing and clearing the same vectors to store objects is efficient. (std::string is similar). If you want to find out exactly how much a vector has allocated in its buffer, call the capacity function and multiply this by the size of the element type. You can call the reserve function to manually increase the buffer size, in expectation of the vector taking more elements shortly.
Maps are more complicated so I don't know. But if you need an associative container, you would have to use something complicated in C too, right?
Just wanted to add to the answers of others few things that I think are important.
Firstly, the default (in implementations I've seen) sizeof(std::vector<T>) is constant and made up of three pointers. Below is excerpt from GCC 4.7.2 STL header, the relevant parts:
template<typename _Tp, typename _Alloc>
struct _Vector_base
{
...
struct _Vector_impl : public _Tp_alloc_type
{
pointer _M_start;
pointer _M_finish;
pointer _M_end_of_storage;
...
};
...
_Vector_impl _M_impl;
...
};
template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
class vector : protected _Vector_base<_Tp, _Alloc>
{
...
};
That's where the three pointers come from. Their names are self-explanatory, I think. But there is also a base class - the allocator. Which takes me to my second point.
Secondly, std::vector< T, Allocator = std::allocator<T>> takes second template parameter that is a class that handles memory operations. It's through functions of this class vector does memory management. There is a default STL allocator std::allocator<T>>. It has no data-members, only functions such as allocate, destroy etc. It bases its memory handling around new/delete. But you can write your own allocator and supply it to the std::vector as second template parameter. It has to conform to certain rules, such as functions it provides etc, but how the memory management is done internally - it's up to you, as long as it does not violate logic of std::vector relies on. It might introduce some data-members that will add to the sizeof(std::vector) through the inheritance above. It also gives you the "control over each bit".
Basically, a vector is just a pointer to an array, along with its capacity (total allocated memory) and size (actually used elements):
struct vector {
Item* elements;
size_t capacity;
size_t size;
};
Of course thanks to encapsulation all of this is well hidden and the users never get to handle the gory details (reallocation, calling constructors/destructors when needed, etc) directly.
As to your performance questions regarding clearing, it depends how you clear the vector:
Swapping it with a temporary empty vector (the usual idiom) will delete the old array: std::vector<int>().swap(myVector);
Using clear() or resize(0) will erase all the items and keep the allocated memory and capacity unchanged.
If you are concerned about efficiency, IMHO the main point to consider is to call reserve() in advance (if you can) in order to pre-allocate the array and avoid useless reallocations and copies (or moves with C++11). When adding a lot of items to a vector, this can make a big difference (as we all know, dynamic allocation is very costly so reducing it can give a big performance boost).
There is a lot more to say about this, but I believe I covered the essential details. Don't hesitate to ask if you need more information on a particular point.
Concerning maps, they are usually implemented using red-black trees. But the standard doesn't mandate this, it only gives functional and complexity requirements so any other data structure that fits the bill is good to go. I have to admit, I don't know how RB-trees are implemented but I guess that, again, a map contains at least a pointer and a size.
And of course, each and every container type is different (eg. unordered maps are usually hash tables).