Containers

The Standard Library containers allow you to group together zero or more items of the same type and access them serially through iterators. Every such object has a begin method that returns an iterator object to the first item and an end function that returns an iterator object for the item after the last item in the container. The iterator objects support pointer-like arithmetic, so that end() - begin() will give the number of items in the container. All container types will implement the empty method to indicate if there are no items in the container, and (except for forward_list) the size method is the number of items in the container. You are tempted to iterate through a container as if it is an array:

    vector<int> primes{1, 3, 5, 7, 11, 13}; 
for (size_t idx = 0; idx < primes.size(); ++idx)
{
cout << primes[idx] << " ";
}
cout << endl;

The problem is that not all containers allow random access, and if you decide it is more efficient to use another container, you'll have to change how the container is accessed. This code also does not work well if you want to write generic code using templates. The previous code is better written using iterators:

    template<typename container> void print(container& items) 
{
for (container::iterator it = items.begin();
it != items.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
}

All of the containers have a typedef member called iterator that gives the type of the iterator returned from the begin method. Iterator objects behave like pointers, so you can obtain the item an iterator refers to using the dereference operator and move to the next item using the increment operator.

For all containers except for vector, there is a guarantee that an iterator will remain valid even if other elements are deleted. If you insert items, then only lists, forward_lists, and associated container guarantee that the iterators remain valid. Iterators will be covered in more depth later.

All containers have to have an exception safe (nothrow) method called swap, and (with two exceptions) they must have transactional semantics; that is, an operation must succeed or fail. If the operation fails, the container is in the same state as before the operation is called. For every container, this rule is relaxed when it comes to multi-element inserts. If you insert many items at a time using an iterator range, for example, and the insert fails for one of the items in the range, then the method will not be able to undo the previous inserts.

It is important to point out that objects are copied into containers, so the type of the objects that you put into a container must have a copy and copy assignment operator. Also, be aware that if you put a derived class object into a container that requires a base class object, then the copying will slice the object, meaning that anything to do with the derived class is removed (data members and virtual method pointers).