270 Chapter 7: STL Containers
7.3 Vectors
‘A vector models a dynamic array. Thus, a vector is an abstraction that manages its elements with a
dynamic C-style array (Figure 7.2). However, the standard does not specify that the implementation
uses a dynamic array. Rather, this follows from the constraints and specification of the complexity
of its operation,
Figure 7.2. Structure of a Vector
To use a vector, you must include the header file
:
#include
There, the type is defined as a class template inside namespace sta:
namespace std {
template >
class vector;
3
The elements of a vector may have any type T. The optional second template parameter defines the
memory model (see Chapter 19). The default memory model is the model allocator, which is
provided by the C+ standard library.
7.3.1 Abil
‘A vector copies its elements into its internal dynamic array. The elements always have a certain
order, Thus, a vector is a kind of ordered collection. A vector provides random access. Thus, you
can access every element directly in constant time, provided that you know its position. The iterators
are random-access iterators, so you can use any algorithm of the STL.
Vectors provide good performance if you append or delete elements at the end. If you insert or
delete in the middle or at the beginning, performance gets worse. This is because every element
behind has to be moved to another position. In fact, the assignment operator would be called for
every following clement.
Size and Capacity
Part of the way in which vectors give good performance is by allocating more memory than they
need to contain all their elements. To use vectors effectively and correctly, you should understand
how size and capacity cooperate in a vector.7.3 Vectors 211
Vectors provide the usual size operations size(), empty (), and max_size() (see Section 7.1.2,
page 254), An additional “size” operation is the capacity () function, which returns the number of
elements a vector could contain in its actual memory. If you exceed the capacity (), the vector has
to reallocate its internal memory.
‘The capacity of a vector is important for two reasons:
1. Reallocation invalidates all references, pointers, and iterators for elements of the vector.
2. Reallocation takes time.
Thus, if a program manages pointers, references, or iterators into a vector, or if speed is a goal, it is
important to take the capacity into account.
To avoid reallocation, you can use reserve() to ensure a certain capacity before you really need
it. In this way, you can ensure that references remain valid as long as the capacity is not exceeded:
std::vector v; — //create an empty vector
v.reserve(80); reserve memory for 80 elements
Another way to avoid reallocation is to initialize a vector with enough elements by passing additional
arguments to the constructor. For example, if you pass a numeric value as parameter, it is taken as
the starting size of the vector:
std::vector v(S); — /creates a vector and initializes it with five values
1 (calls five times the default constructor of type T)
Of course, the type of the elements must provide a default constructor for this ability. For fundamen-
tal types, zero initialization (see Section 3.2.1, page 37) is guaranteed. But note that for complex
types, even if a default constructor is provided, the initialization takes time. If the only reason for
initialization is to reserve memory, you should use reserve().
‘The concept of capacity for vectors is similar to that for strings (see Section 13.2.5, page 669),
with one big difference: Unlike for strings, it is not possible to call reserve () for vectors to shrink
the capacity, Calling reserve() with an argument that is less than the current capacity is a no-op.
Furthermore, how to reach an optimal performance regarding speed and memory use is implementa-
tion defined, Thus, implementations might increase capacity in larger steps. In fact, to avoid internal
fragmentation, many implementations allocate a whole block of memory (such as 2K) the first time
you insert anything if you don’t call reserve() first yourself, This can waste a lot of memory if
you have many vectors with only a few small elements
Because the capacity of vectors never shrinks, it is guaranteed that references, pointers, and
iterators remain valid even when elements are deleted, provided that they refer to a position before
the manipulated elements. However, insertions invalidate all references, pointers, and iterators when
the capacity gets exceeded.
C++11 introduced a new member function for vectors: a nonbinding request to shrink the capac-
ity to fit the current number of elements:
veshrinkto_£it(); — /request to shrink memory (since C++11)
This request is nonbinding to allow latitude for implementation-specific optimizations. Thus, you
cannot expect that afterward v.capacity==v.size() yields true.
Before C+#11, there you could shrink the capacity only indirectly: Swapping the contents with
another vector swaps the capacity. The following function shrinks the capacity while preserving the
elements:272 Chapter 7: STL Containers
template
void shrinkCapacity(std: :vector& v)
t
std::vector tmp(v 1 copy elements into a new vector
v.swap(tmp) ; / swap internal vector data
}
‘You could even shrink the capacity without calling this function by calling the following statement:5
1/ shrink capacity of vector v for type T
std: :vector(v) .swap(v);
However, note that after swap (), all references, pointers, and iterators swap their containers. They
still refer to the elements to which they referred on entry. Thus, shrinkCapacity() invalidates all
references, pointers, and iterators. The same is true for shrink_to_fit ().
Operation Effect
vector ¢ Default constructor, creates an emply vector without any
elements
vector ¢(c2) Copy constructor; creates a new vector as a copy of e2 (all
elements are copied)
vector ¢ = c2 Copy constructor; creates a new vector as a copy of c2 (all
elements are copied)
vector ¢(r) Move constructor; creates a new vector, taking the contents
of the rvalue rv (since C++11)
vector ¢ = rv Move constructor; creates a new vector, taking the contents
of the rvalue rv (since C+411)
vector c(n) Creates a vector with n elements created by the default
constructor
vector c(n,elem) | Creates a vector initialized with n copies of clement elem
vector c(beg,end) | Creates a vector initialized with the elements of the range
[beg.end)
vector c(initlist) | Creates a vector initialized with the elements of initializer
list initlist (since C++11)
initlist | Creates a vector initialized with the elements of initializer
list initlist (since C++11)
c."vector() Destroys all elements and frees the memory
vector
Table 7.9. Constructors and Destructor of Vectors
* You (or your compiler) might consider this statement as being incorrect because it calls a nonconstant member
function for a temporary value, However, standard C++ allows you to call a nonconstant member function for
temporary values.7.3 Vectors 273
7.3.2. Vector Operations
Create, Copy, and Destroy
Table 7.9 lists the constructors and destructors for vectors. You can create vectors with and without
elements for initialization. If you pass only the size, the elements are created with their default
constructor. Note that an explicit call of the default constructor also initializes fundamental types,
such as int, with zero (see Section 3.2.1, page 37). See Section 7.1.2, page 254, for some remarks
about possible initialization sources.
Nonmodifying Operations
Table 7.10 lists all nonmodifying operations of vectors. See additional remarks in Section 7.1.2,
page 254, and Section 7.3.1, page 270.
Operation Effect
e.euptyO Returns whether the container is empty (equivalent to sizeQ)
might be faster)
c.size() Returns the current number of elements
c.max_size() Returns the maximum number of elements possible
c.capacity() Returns the maximum possible number of elements without
reallocation
c.reserve(num) | Enlarges capacity, if not enough yet®
c.shrink_to_fit() | Request to reduce capacity to fit number of elements (since C++11)®
ct == 2 Returns whether c1 is equal to ¢2 (calls == for the elements)
ct != 2 Retums whether c1 is not equal to ¢2 (equivalent to ! (ct==¢2))
ct < 2 Retums whether c1 is less than ¢2
cl > cB Returns whether cf is greater than ¢2 (equivalent to c2= 2 Returns whether cf is greater than or equal to ¢2 (equivalent to
'(el 1;
std: :vector coll;
MU make coll be a copy of the contents of L
coll.assign(1.begin() ,1.end());
Element Access
To access all elements of a vector, you must use range-based for loops (see Section 3.1.4, page 17),
specific operations, or iterators. Table 7.12 shows all vector operations for direct element access. AS
usual in C and C++, the first element has index 0, and the last element has index size()-1. Thus,
the nth element has index n-1. For nonconstant vectors, these operations return a reference to the
element, Thus, you could modify an element by using one of these operations, provided it is not
forbidden for other reasons.
Operation | Effect
elidel Retums the clement with index idx (no range checking)
c.at(idx) | Returns the element with index idx (throws range-error exception
if idx is out of range)
¢.front() | Returns the first element (no check whether a first element exists)
c.back() _| Returns the last element (no check whether a last element exists)
Table 7.12. Direct Element Access of Vectors
The most important issue for the caller is whether these operations perform range checking.
Only at() performs range checking. If the index is out of range, at () throws an out_of_range7.3 Vectors 275
exception (see Section 4.3, page 41). Alll other functions do not check. A range error results in
undefined behavior. Calling operator [], front (), and back() for an empty container always
results in undefined behavior:
std: :vector coll; Mempty!
coll[5] = elem; 1/ RUNTIME ERROR => undefined behavior
std::cout << coll.front(); | //RUNTIME ERROR = undefined behavior
So, you must ensure that the index for operator (/] is valid and that the container is not empty when
either front () or back() is called:
std: :vector coll; Hempty!
if (coll.size() > 5) {
coll[5] = elem; HOK
+
if (Icoll.empty()) {
cout << coll.front(); | / OK
}
coll.at(5) = elem; M throws out_of range exception
Note that this code is OK only in single-threaded environments. In multithreaded contexts, you need
synchronization mechanisms to ensure that co11 is not modified between the check for its size and
the access to the element (see Section 18.4.3, page 984, for details).
Iterator Functions
Vectors provide the usual operations to get iterators (Table 7.13). Vector iterators are random-access
iterators (see Section 9.2, page 433, for a discussion of iterator categories). Thus, in principle you
could use all algorithms of the STL.
‘The exact type of these iterators is implementation defined. For vectors, however, the iterators
retumed by begin(), cbegin(), end(), and cend() are often ordinary pointers, which is fine
because vectors usually use a C-style array for the elements and ordinary pointers provide the inter-
face of random-access iterators. However, you can’t count on the fact that the iterators are ordinary
pointers. For example, if a safe version of the STL that checks range errors and other potential prob-
lems is used, the iterator type is usually an auxiliary class. See Section 9.2.6, page 440, for a nasty
difference between iterators implemented as pointers and iterators implemented as classes.
Iterators remain valid until an element with a smaller index gets inserted or removed or until
reallocation occurs and capacity changes (see Section 7.3.1, page 270)
Inserting and Removing Elements
Table 7.14 shows the operations provided for vectors to insert or to remove elements. As usual when
using the STL, you must ensure that the arguments are valid. Iterators must refer to valid positions,
and the beginning of a range must have a position that is not behind the end,276 Chapter 7: STL Containers
‘Operation _| Effect
e.begin®) | Returns a random-access iterator for the first clement
c.end() Returns a random-access iterator for the position after the last element
c.cbegin() | Returns a constant random-access iterator for the first element (since C+11)
c.cend() Returns a constant random-access iterator for the position after the last element
(since C+411)
c.rbegin() | Returns a reverse iterator for the first element of a reverse iteration
c.rend() Returns a reverse iterator for the position after the last element of a reverse
iteration
c.crbegin() | Retums a constant reverse iterator for the first element of a reverse iteration
(since C+411)
c.crend() | Returns a constant reverse iterator for the position after the last element of a
reverse iteration (since C+#11)
Table 7.13. Iterator Operations of Vectors
As usual, it is up to the programmer to ensure that the container is not empty when pop_back() is
called. For example:
std: :vector coll; Mempty!
coll.pop_back() ; // RUNTIME ERROR => undefined behavior
if (Icoll.empty()) {
coll. pop_back(); WOK
3
However, note that in a multithreaded context you have to ensure that col doesn’t get modified
between the check for being empty and pop_back() (see Section 18.4.3, page 984).
Regarding performance, you should consider that inserting and removing happens faster when
e Elements are inserted or removed at the end.
© The capacity is large enough on entry.
© Multiple elements are inserted by a single call rather than by multiple calls,
Inserting or removing elements invalidates references, pointers, and iterators that refer to the follow-
ing elements, An insertion that causes reallocation invalidates all references, iterators, and pointers,
Vectors provide no operation to remove elements directly that have a certain value. You must
use an algorithm to do this. For example, the following statement removes all elements that have the
value val:
sti
vector coll;
1 remove all elements with value val
coll.erase(remove(coll.begin() ,coll.end() ,
val),
coll.end());
This statement is explained in Section 6.7.1, page 2187.3 Vectors
271
Operation
Effect
¢. push_back (elem)
c.pop_back ()
c.insert (pos, elem)
c. insert (pos,n, elem)
c.insert (pos, beg ,end)
c.insert (pos, initlist)
c.emplace(pos,args.
c.emplace_back (args...)
c.erase (pos)
c.erase (beg ,end)
c.resize (num)
c.resize (num, elem)
c.clear()
“Appends a copy of elem at the end
Removes the last element (does not return it)
Inserts a copy of elem before iterator position pos and returns
the position of the new element
Inserts n copies of elem before iterator position pos and returns
the position of the first new element (or pos if there is no new
element)
Inserts a copy of all elements of the range [beg,end) before
iterator position pos and returns the position of the first new
element (or pos if there is no new element)
Inserts a copy of all elements of the initializer list initlist before
iterator position pos and returns the position of the first new
element (or pos if there is no new element; since C++11)
Inserts a copy of an element initialized with args before
iterator position pos and returns the position of the new
element (since C++11)
Appends a copy of an element initialized with args at the end
(returns nothing; sinee C++11)
Removes the element at iterator position pos and returns the
position of the next element
Removes all elements of the range [beg,end) and returns the
position of the next element
Changes the number of elements to num (if size() grows new
elements are created by their default constructor)
Changes the number of elements to num (if size() grows new
elements are copies of elem)
Removes all elements (empties the container)
Table 7.14,
To remove only the first element that has a certain value, you must use the following statement
std: :vector col:
Insert and Remove Operations of Vectors
MW remove first element with value val
std: :vector
pos = find(coll.begin()
val);
if (pos
coll.erase(pos) ;
terator pos;
,coll.end(),
coll.end()) {278 Chapter 7: STL Containers
7.3.3 Using Vectors as C-Style Arrays
As for class array<>, the C++ standard library guarantees that the elements of a vector are in
contiguous memory. Thus, you can expect that for any valid index 4 in vector v, the following yields
true:
&v(i] == evlo] +i
This guarantee has some important consequences. It simply means that you can use a vector in all
cases in which you could use a dynamic array. For example, you can use a vector to hold data of
ordinary C-strings of type char* or const char*:
std: :vector v; UW create vector as dynamic array of chars
v.resize(41) ; MW make room for 41 characters (including *\0?)
strepy(kv[0],"hello, world"); /copy a C-string into the vector
printé("%s\n", &v[0]); 1/ print contents of the vector as C-string
Note, however, that since C++11, you don’t have to use the expression & [0] to get direct access to
the elements in the vector, because the member function data () is provided for this purpose:
sti
vector v; UW create static array of 41 chaxs
strepy(v.data() "hello, world"); //copy a C-string into the array
print#("%s\n", v.data()); 1 print contents of the array as C-string
Of course, you have to be careful when you use a vector in this way (just as you always have to be
careful when using ordinary C-style arrays and pointers). For example, you have to ensure that the
size of the vector is big enough to copy some data into it and that you have an *\0? element at the
end if you use the contents as a C-string. However, this example shows that whenever you need an
array of type T for any reason, such as for an existing C library, you can use a vector and pass
the address of the first element.
Note that you must not pass an iterator as the address of the first element. Iterators of vectors
have an implementation-specific type, which may be totally different from an ordinary pointer:
\n", v.begin()); // ERROR (might work, but not portable)
\n", v.data()); WOK (since C++11)
s\n", &v[0]) 5 OK, but data() is better
7.3.4 Exception Handling
‘Vectors provide only minimal support for logical error checking. The only member function for
which the standard requires that it may throw an exception is at (), which is the safe version of
the subscript operator (see Section 7.3.2, page 274). In addition, the standard requires that only the
usual standard exceptions may occur, such as bad_alloc for a lack of memory or exceptions of
user-defined operations.
If functions called by a vector (functions for the element type or functions that are user-supplied)
throw exceptions, the C++ standard library provides the following guarantees:7.3 Vectors 279
1. If an element gets inserted with push_back() and an exception occurs, this function has no
effect.
2. insert (), emplace(), emplace_back(), and push_back() either succeed or have no effect,
provided that the copy/move operations (constructors and assignment operators) of the elements
do not throw.
3. pop_back() does not throw any exceptions.
4. erase() does not throw if the copy/move operations (constructors and assignment operators) of
the elements do not throw.
5. swap() and clear() do not throw.
6. If elements are used that never throw exceptions on copy/move operations (constructors and
assignment operators), every operation is either successful or has no effect, Such elements might
be “plain old data” (POD). POD describes types that use no special C++ feature. For example,
every ordinary C structure is POD.
All these guarantees are based on the requirements that destructors don’t throw. See Section 6.12.2,
page 248, for a general discussion of exception handling in the STL.
7.3.5 Examples of Using Vectors
The following example shows a simple use of vectors:
// cont/vector1.cpp
#include
#include
#include
#include
#include
using namespace std;
int main()
{
1/ create empty vector for strings
vector sentence;
M/ reserve memory for five elements to avoid reallocation
sentence. reserve(5) ;
W/ append some elements
sentence. push_back("Hello,");
sentence. insert (sentence. end() ,{"how", "are", "you" ,"?"});
UW print elements separated with spaces
copy (sentence.cbegin(), sentence.cend(),
ostream_iterator(cout," "));280 Chapter 7: STL Containers
cout << endl;
print “technical data"”
cout <<" max_size( " << sentence.max_size() << endl;
cout <<" size(): " << sentence.size() << endl;
cout <<" capacity(): " << sentence.capacity() << endl;
1 swap second and fourth element
swap (sentence(1], sentence[3]);
insert element "always" before element "?:
sentence. insert (find(sentence.begin() ,sentence.end() ,"?"),
always") ;
Hassign "\" to the last element
sentence.back() = "!";
1/ print elements separated with spaces
copy (sentence.cbegin(), sentence.cend(),
ostream_iterator(cout," "));
cout << endl;
1 print some “‘technical data’’ again
cout <<" size(): " << sentence.size() << endl;
" << sentence.capacity() << endl;
cout <<" capacity(
MW delete last two elements
sentence. pop_back() ;
sentence. pop_back();
// shrink capacity (since C++11)
sentence.shrink_to_fit();
1 print some “technical data’’ again
cout <<" size(): " << sentence.size() << endl;
cout <<" capacity(): " << sentence.capacity() << endl;
‘The output of the program might look like this:
Hello, how are you ?
max_size(): 1073741823
size(): 5
capacity(): 57.3 Vectors 281
Hello, you are how always !
size(): 6
capacity(): 10
size(): 4
capacity(): 4
Note my use of the word might. The values of max_size() and capacity () are unspecified and
might vary from platform to platform. Here, for example, you can sce that the implementation seems
to double the capacity if the capacity no longer fits and not necessarily shrinks if there is a request
to do so.
7.3.6 Class vector
For Boolean elements, the C++ standard library provides a specialization of vector<>. The goal
is to have a version that is optimized to use less size than a usual implementation of vector<>
for type bool. Such a usual implementation would reserve at least 1 byte for each element, The
vector specialization usually uses internally only 1 bit for an element, so it is typically
ight times smaller, But such an optimization also has a snag: In C++, the smallest addressable
value must have a size of at least 1 byte. Thus, such a specialization of a vector needs special
handling for references and iterators.
As a result, a vector does not meet all requirements of other vectors. For example, a
vector: :reference is not a true Ivalue and vector: : iterator is not a random-
access iterator. Therefore, template code might work for vectors of any type except bool. In ad-
dition, vector might perform worse than normal implementations, because element opera-
tions have (o be transformed into bit operations. However, how vector is implemented is
implementation specific. Thus, the performance (speed and memory) might differ
Note that class vector is more than a specialization of vector<> for bool. It also
provides some special bit operations. You can handle bits or flags in a more convenient way.
vector has a dynamic size, so you can consider it a bitfield with dynamic size. Thus,
you can add and remove bits. If you need a bitfield with static size, you should use bitset rather
than a vector. Class bitset is covered in Section 12.5, page 650.
The additional operations of vector are shown in Table 7.1
Operation Effect
2.#1ipO Negales all Boolean elements (complement of all bits)
elidx] .£11p0 Negates the Boolean element with index idx (complement of a
single bit)
clidx] = val Assigns val to the Boolean clement with index idx (assignment to a
single bit)
clidx!] = clidx2] | Assigns the value of the element with index idx2 to the element
with index idx
Table 7.15. Special Operations of vector