Reliable Software Logo

C++ In Action: Language

Embedded objects

Embeddings, initialization of embeddings, order of construction/destruction.


We've seen data members of type int--one of the built in types. The beauty of C++ is that it makes virtually no distinction between built in types and the ones defined by the programmer. When a data member of some class is of a user defined type, it is called an embedded object. In the following example Matter is embedded in World (I just gave matter identity. You know, this matter here is much more stable than the one in the neighboring universe.) Another way to put it is--World contains Matter.

#include <iostream>

class Matter
{
public:
    Matter (int id)
        : _identifier(id)
    {
        std::cout << "  Matter for " << _identifier << " created\n";
    }
    ~Matter ()
    {
        std::cout << "  Matter in " << _identifier << " annihilated\n";
    }
private:
    const int _identifier;
};

class World
{
public:
    World (int id)
        : _identifier (id), _matter (_identifier) // initializing embeddings
    {
        std::cout << "Hello from world " << _identifier << ".\n";
    }

    ~World ()
    {
        std::cout << "Good bye from world " << _identifier << ".\n";
    }
private:
    const int      _identifier;
    const Matter   _matter; // Embedded object of type Matter
};

World TheUniverse (1);

int main ()
{
    World myWorld (2);
}
					

What's interesting in this example is the preamble to the constructor of World.

World (int id)
        : _identifier (id), _matter (_identifier)

It first initializes the _identifier and then _matter. Initializing an object means calling its constructor. The constructor of Matter requires an integer, and that's what we are passing there. We made _matter const for the same reason we made _identifier const. It is never changed during the lifetime of the World. It is initialized in the preamble and never even accessed by anybody. By the way, the double slash // is used for comments. The compiler ignores everything that follows // until the end of line. And now for the surprise: The order of initialization has nothing to do with the order in the preamble. In fact, if the constructor of the embedded object doesn't require any arguments, it can be omitted from the preamble whatsoever. If no explicit initialization is required, the whole preamble may be omitted.

Instead, the rule of initialization is:

Data members are initialized in the order in which they are embedded in the class definition.

Since _identifier is embedded before _matter in the definition of World, it will be initialized first. That's why we could use its value to in the construction of _matter. The order of embeddings in C++ is as important as the order of statements.

The destruction of embedded objects is guaranteed to proceed in the opposite order of construction. The object that was constructed first will be destroyed last, and so on.

In the object-oriented argot, object embedding is called the "has-a" relationship. A World has a Matter (remember, we have objectified matter).

has-a

Figure 4 Graphical representation of the has-a relationship.


To summarize, objects of programmer defined types may be embedded as data members in other classes. The constructors for the embeddings are called before the body of the object's constructor is executed (conceptually you may visualize them as being called in the preamble). If these constructors need arguments, they must be passed in the preamble. The order of construction is determined by the order of embeddings. The embeddings are destroyed in the reverse order of construction.


NextNext: Inheritance