I didn't just pull Example 7-1(c) out of a hat. It turns out there are a lot of real-world C++
programmers who have done exactly this, and with good reason, because code like that in Example 7- 1(c) illustrates some of the important benefits that come from using a
vector
:• We don't have to know the potential size of
c
in advance. In many C programs, arrays are allocated pessimistically so that they'll be big enough for most intended uses. Unfortunately, this means that space is needlessly wasted much of the time when the full size isn't needed, after all. (Worse, it means the program breaks if it ever ends up needing more space than we'd thought.) With a C++vector
, we can grow the container as needed instead of just guessing. Also, if we do happen to know in advance how many objects we're going to put into thevector
, we can simply say so (by first callingc
.reserve(100)
, if we want to reserve space for 100 elements), so there's no performance disadvantage with avector
compared to an array, because we have a way to avoid repeated reallocations as we insert new elements into thevector
.• We don't have to separately track the actual length of the array so that we can pass it as the final (
nLength
) parameter toFindCustomer()
. Yes, you can use a C array and use workarounds that make remembering or recalculating the length easier,[12] but none of thoseworkarounds are as easy and safe as just writing
c
.size()
.[12] The most typical approaches are to use a
const
value or a#defined
name for the size ofthe array, or else a macro that expands to
sizeof(c)/sizeof(c[0])
to compute the size of the array.In short, as far as I know, Example 7-1(c) has always worked on every implementation of
std::vector
that has ever existed.Until 2001, a minor fly in the ointment was that the 1998 C++ standard [C++98] didn't actually guarantee that Example 7-1(c) would work portably on every C++ platform you might buy. This is because Example 7-1(c) assumes that the internal contents of the
vector
are stored contiguously in the same format as is an array, but the original C++ standard didn't require vendors to implementcontents in some other way (such as contiguously but in backwards order, or in sequential order but with an extra byte of housekeeping information between elements), then Example 7-1(c) would, in fact, fail miserably. This was fixed in 2001 as part of Technical Corrigendum #1 to the C++ standard, which now requires that the contents of a
vector
are stored contiguously, just like an array. Even if your compiler doesn't include support for the Technical Corrigendum yet, though, Example 7- 1(c) should work in practice today. Again, I don't know of a commercialvector
implementation that doesn't store its elements contiguously.Bottom line: Prefer using
vector
(ordeque
; see below) instead of arrays.In Most Cases, Prefer Using vector
1. In the standard library,
vector
anddeque
provide similar services. Which should you typically use? Why? Under what circumstances would you use the other?The C++ standard [C++98], in section 23.1.1, offers some advice on which containers to prefer. It says:
vector
is the type of sequence that should be used by default. …deque
is the data structure of choice when most insertions and deletions take place at the beginning or at the end of the sequence.vector
anddeque
offer nearly identical interfaces and are generally interchangeable.deque
also offerspush_front()
andpop_front()
, whichvector
does not. (True,vector
offers
capacity()
andreserve()
, whichdeque
does not, but that's no loss. Those functions are actually something of a weakness invector
, as I'll demonstrate in a moment.) The main structural difference betweenvector
anddeque
lies in the way the two containers organize their internal storage. Under the covers, adeque
allocates its storage in pages, or "chunks," with a fixed number of contained elements in each page. This is why adeque
is often compared to (and pronounced as) a "deck" of cards,[13] although its name originally came from "double-endedqueue" because of its ability to insert elements efficiently at either end of the sequence. On the other hand, a
vector
allocates a contiguous block of memory, and can only insert elements efficiently at the end of the sequence.[13] The first use I know of the "deck of cards" analogy for
deque
was by Donald Knuth in [Knuth97].The paged organization of a
deque
offers several benefits:• A
deque
offers constant-timeinsert()
anderase()
operations at the front of the container, whereas avector
does not, hence the note in the standard about using adeque
if you need to insert or erase at both ends of the sequence.• A
deque
uses memory in a more operating system-friendly way. For example, a 10-megabyte
vector
uses a single 10-megabyte block of memory. On some operating systems that single block can be less efficient in practice than a 10-megabytedeque
that can fit in a series of smaller blocks of memory. On other operating systems, a contiguous address space can be built of smaller blocks under the covers anyway. On such platforms, thisdeque
noncontiguity advantage won't matter.• A
deque
is somewhat easier to use, and inherently more efficient for growth, than avector
. The only operations supplied byvector
thatdeque
doesn't have arecapacity()
andreserve()
. That's becausedeque
doesn't need them! Forvector
, callingreserve()
before a large number ofpush_back()
s can eliminate reallocating ever-larger versions of the same buffer every time it finds out that the current one isn't big enough, after all. Adeque
has no such problem, and having adeque::reserve()
before a large number ofpush_back()
s would not eliminate any allocations (or any other work), because none of the allocations is redundant. Thedeque
has to allocate the same number of extra pages whether it does it all at once or as elements are actually appended.Interestingly, note that within the C++ standard itself, the
stack
adapter prefersdeque
. Even though astack
can only grow in one direction and so never needs insertion in the middle or at the other end, the default implementation is required to be adeque
:namespace std
{
template<typename T, typename Container = deque<T> >
class stack
{
// ...
};
}
Despite this, for readability and simplicity, prefer using
vector
by default in your programs unless you know you need to efficiently insert and erase at the beginning of the container and don't need the underlying objects to be stored contiguously. Note thatvector
is to C++ what the array is to C— the default container type to use in your programs, and one that does a reasonably good all-around job unless you know you need something different.The Incredible Shrinking vector
As already noted, it's nice that
vector
manages its own storage, and that we can give it hints about how much capacity to keep ready under the covers as it grows. If we have avector<T> c
that we're about to grow to contain 100 elements, we can first callc
.reserve(100)
and incur, at most, a single reallocation hit. This allows optimally efficient growth.But what if we're doing the opposite? What if we're using a
vector
that's pretty big, and then we remove elements we no longer need and want thevector
to shrink to fit—that is, we want it to get rid of the now-unneeded extra capacity? You might think that the following would work:2. What does the following code do?