Delegation and "containership" relate to embedding objects as
opposed to employing inheritance. Both achieve the same thing in
different ways but if a class is not intended to be used with
inheritance (e.g., it has no virtual destructor), then embedding an
object of the class is the only option.
When you embed an object you typically want to expose an
interface to that object, but may not wish to expose the object's
complete interface. In some cases you may wish to simplify the
interface, in others you may wish to enhance it. This is achieved
by declaring delegates, which are really nothing more than proxy
functions that invoke the object's methods on your behalf, often
simplifying the calls to those methods or enhancing them in some
way. Delegates are essentially the embedded equivalent of
overriding a base class virtual method.
For classes that cannot be inherited, embedding is the only
option. However, once embedded, your new class can then be
inherited. Thus embedding is often used to provide thin wrappers to
enable inheritance. You cannot inherit from the embedded object, of
course, but you can inherit from the thin wrapper, provide your
thin wrapper includes a virtual destructor along with one or more
virtual methods.
The STL containers are typical examples because they are not
designed to be inherited from. Many see this as a bad thing, but it
is actually a good thing. The STL is not a class hierarchy as such
-- it is largely a framework of completely separate types, adaptors
and algorithms that are "wired" together by iterators. It is not
strictly object-oriented, but then object-oriented programming is
not a magic bullet to every type of problem. The STL containers are
designed to be both efficient and generic but are not intended to
act as base classes. Although the STL could have provided thin
wrappers for inheritance purposes, there are so many different ways
to implement thin-wrappers that no single wrapper can ever cater
for every programmer's needs. Thus it is left to the programmers
themselves to create their own specific wrappers as and when they
require them, including as much or as little generic behaviour as
they need.
As an example, C++ does not include a generic "matrix" data
type. Many other languages do, so it seems strange that C++ does
not. However, those languages that do provide a matrix data type
have to cater for everyone, and this inevitably leads to
unavoidable inefficiencies. But with C++, you can simply implement
your own to do exactly what you want as efficiently as possible
using whatever combination of containers best suits your needs,
with as much or as little generics as you required. C++ is nothing
if not flexible.