2.10.  Class Declarations and Defintions

[ fromfile: classdeclarations.xml id: classdeclarations ]

Figure 2.4.  Bidirectional Relationship

Bidirectional Relationship

Bidirectional relationships not very different from Figure 2.4 appear quite often in classes. To implement them, the compiler needs to have some knowledge of each class before defining the other. At first, we might attempt to #include each header file from the other, as shown in Example 2.12.

Example 2.12. src/circular/badegg/egg.h

[ . . . . ]
#include "chicken.h"
class Egg {
 public:
    Chicken* getParent(); 
};
[ . . . . ]

The problem becomes clear when we look at Example 2.13, which analogously includes egg.h.

Example 2.13. src/circular/badegg/chicken.h

[ . . . . ]
#include "egg.h"

class Chicken {
 public:
    Egg* layEgg();
};
[ . . . . ]

The preprocessor cannot deal with circular dependencies such as these. In fact, neither header file needed to include the other. In each case, doing so created an unnecessary strong dependency between header files.

Lucky for us, we can use forward class declarations instead of including headers. Declared classes can be used as types for pointers or references, as long as they are not de-referenced in the file.

Example 2.14. src/circular/goodegg/egg.h

[ . . . . ]
class Chicken; 1
class Egg {
 public:
    Chicken* getParent(); 2 
};
[ . . . . ]

1

Forward class declaration

2

Okay in declarations if they are pointers

We can define the getParent() in the code module, egg.cpp, shown in Example 2.15. Notice the .cpp file can include both header files without causing a circular dependency between them. The .cpp has a strong dependency on both headers, while the header files have no dependency on each other.

Example 2.15. src/circular/goodegg/egg.cpp

#include "chicken.h"
#include "egg.h"

Chicken* Egg::getParent() {
    return new Chicken(); 1   
}

1

requires definition of Chicken

Forward class declarations make it possible to define circular bidirectional relationships, such as the one above. We pushed down the dependencies on headers into the source code modules that actually needed them.

In Java, it is possible to create circular strong bidirectional dependencies between two (or more) classes. In other words, each class can import and use (dereference references to) the other. This kind of circular dependency makes both classes much more difficult to maintain, since changes in each can break the other. This is one situation where C++ protects the programmer better than Java does - it is not possible to create such a relationship accidentally.

Java also offers a forward class declaration, but it is rarely used since Java has no separate header file and implementation file.

For further details, see Section C.1