2.10.  Class Declarations and Definitions

[ 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, you might think that each header file should #include 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 you 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 does not permit circular dependencies such as these. In this example, neither header file needed to include the other. In each case, doing so created an unnecessarily strong dependency between header files.

Under the right conditions, C++ permits you to use a forward class declaration instead of #including a particular header.

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.


A forward class declaration enables you to refer to a symbol without having its full definition available. It is an implicit promise to the compiler that the definition of the class will be #included when it is needed. Classes that are declared but not defined can only be used as types for pointers or references, as long as they are not dereferenced in the file.

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

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.


Thus, forward class declarations make it possible to define bidirectional relationships, such as the one above without creating circular #includes. We located the dependencies in the source code modules that actually needed them instead of in the header files.

In Java, you can 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 because changes in either one can break the other. This is one situation where C++ protects the programmer better than Java does: You cannot create such a relationship accidentally.

Java also offers a forward class declaration, but it is rarely used because Java programs do not use separate header and implementation files.

For further details, see Section C.2