10.5. Aggregation: Pointer Member Variables

Time: 00:02:17 | Download: Large, Large (CC), Small | Streaming (CC) | Slides (PDF)

We limited ourselves to non-pointer member variables in our previous discussion of classes and objects. Adding pointer members increases the power and flexibility of object-oriented programs and allows programmers to implement more UML class relationships. Nevertheless, allowing pointer members also increases the complexity and effort of programming with classes and objects.

The textbook uses two non-standard, working definitions to make it easier for us to talk about pointer and non-pointer class members:
A simple class or object is:
A class or object without any pointer member variables.
A complex class or object is:
A class or object with one or more pointer member variables.
A UML class diagram showing three private member variables. The first is a string pointer called name, the second is an integer called weight, and the last is a double called height.
class Person
{
    private:
	string*	name;
	int	weight;
	double	height;
};
An abstract representation of what an instance of the Person class looks like in memory. A large rectangle represents the Person object, with three small rectangles inside. The smaller rectangles represent the three member variables: name, weight, and height. An arrow points from the name rectangle to a fourth rectangle, drawn outside of the <kbd>Person</kbd> rectangle, representing an instance of the string class - the pointer variable points to the string object.
(a)(b)(c)
A class with a pointer member variable.
  1. A UML class diagram illustrating the member variables in the Person class. UML diagrams are language agnostic; nevertheless, we include the asterisk to emphasize that the name member is a pointer.
  2. The C++ specification of class Person. The member variable name points to an instance of the string class.
  3. Making a member variable a pointer keeps the data separate from the object. In this example, the person's name is in a string object existing outside of the Person.
UML Classes Aggregation Classes Abstract Representation
An aggregation hierarchy. 'Car' is the whole class with two parts: 'Engin' and 'Transmission.' The aggregation symbol is formed by a line with a hollow or outlined diamond at one end, attached to Car, and a plain end, attached to Engine and Transmission.
class Engine
{
	. . .
};

class Transmission
{
	. . .
};

class Car
{
    private:
        Engine *	cars_engine;
        Transmission *	cars_transmission;
    . . .
};
A car object with two pointer member variables. One member points to an engine object, and the other to a Transmission object. When a program creates a whole object (Car) with aggregated parts (Engine and Transmission), it only creates pointer variables. The program creates the parts with the whole constructor, setter functions, or in other classes - whatever the problem demands. Pointers make the binding between a whole and its parts weak or loose.
Building a whole-part object with aggregation. In this example, a Car (the whole) consists of an Engine and a Transmission (the parts). We implement the whole-part relationship with two pointer member variables (highlighted with blue and red) in the Car class. When a program instantiates the Car class, it only creates pointers; the Engine and Transmission objects are created elsewhere in the program and exist outside the Car. As aggregation is a whole-part relationship, we can say that "a Car has an Engine" or that "a Transmission is part of a Car."

Although the Engine and Transmission details are not significant for this example, their class specifications must precede the Car specification. If we specify the classes in a single file, we must do so as illustrated here. Specifying the classes in separate header files requires the program to #include the part header files before specifying the whole. The next sections describe how to initialize and use part objects bound to the whole with pointers.

Syntactically, adding a pointer member variable to a class is seemingly trivial - adding an asterisk to one or more member variables. But conceptually, the change has significant consequences. When we change a member variable from a non-pointer to a pointer, we also change the relationship from composition to aggregation. Changing the relationship means changing a strong, tight binding to a loose, weak one. Aggregated objects may have different lifetimes, the whole can share its parts with other program objects, and the whole can discard or replace its parts whenever convenient. The increase in complexity requires us to explore five distinct concepts related to pointer member variables:

  1. Initializing pointer members with constructors and setters
  2. Destroying (i.e., deleting) objects with destructors
  3. Using pointer members
  4. Maintaining aggregation security by testing for a nullptr
  5. Overriding the copy constructor

We explore these concepts in the following sections.

Aggregation Summary: Filling In The Table

Aggregation is a constructive relationship conveniently characterized by the phrase "has a." It has many property values in common with composition, but it differs in the strength or tightness of the binding between the objects. The following figure summarizes aggregation's property values. Use the summary to check and complete your entries in one of the blank Class Relationship Tables located at the end of the chapter.

  1. Semantics. We can read aggregation in either direction depending on the phrase we use. We read it from the Whole to the Part as a has a relationship and from the Part to the Whole as a part of.
    • a Car has an Engine
    • an Engine is part of a Car
    • a Car has a Transmission
    • a Transmission is a part of a Car
  2. Directionality. Aggregation is a unidirectional or a one-way relationship. Unidirectionality means that
    An aggregation hierarchy with a whole class named Whole and a part class named Part. An aggregation connector, with the outlined diamond attached to Whole, connects the two classes. An adjacent arrow points downwards, indicating aggregation's directionality: Whole to Part. The same UML aggregation hierarchy, but the arrow indicates the message passing direction: Whole to Part. The same UML aggregation hierarchy, but the arrow indicates knowledge: the Whole knows about the Part, but not the other direction. The same UML aggregation hierarchy, but the arrow indicates the direction of navigation: Whole to Part.
    (a)(b)(c)(d)
    1. The operations may only take place in one direction: from the whole to the part
    2. The whole object can send a message to the part object, which can respond to the message but cannot initiate message sending
    3. The whole "knows" about the part, but the part doesn't "know" about the whole
    4. It is possible to navigate from the whole to the part object but not from the part to the whole
  3. Binding Strength. The binding between the two objects is weak or loose because the part object exists outside the whole object and is only connected to it by a pointer (Figure 1). The strength of the binding implies the final two characteristics:
    • Lifetime. The two objects have an independent lifetime, which means that the two objects are created and destroyed at different times. It also means that the relationship between the two objects is changeable - the program can create or break it whenever it is convenient.
    • Sharing. The binding between the two objects is weak or loose enough that the whole can share the part with other objects in the program.
  4. C++ implementation.
    class Car
    {
        private:
            Engine*         my_engine;
            Transmission*   my_transmission;
    };
Aggregation property values. Like all constructive relationships, C++ implements aggregation with class-scope (i.e., member) variables, but the variables are pointers.