- Class Declarations and Definitions
- The Preprocessor: For Including Files
- Conclusions
The Preprocessor: For Including Files
In C++, code reuse is indicated by the presence of a preprocessor directive, #include, at the top of source code files. We #include header files that contain things like class or namespace definitions, const definitions, function prototypes, and so forth. These files are literally included in our own files before the compiler begins to translate our code.
The compiler will report an error if it sees any identifier defined more than once. It will tolerate repeated declarations but not repeated definitions. To prevent repeated definitions, we’re always careful to use an #ifndef wrapper around each header file. This tells the C preprocessor to skip the contents if it has already seen them. Let’s examine the class definition in Listing 5 (line numbers added).
Listing 5 src/preprocessor/constraintmap.h.
1. #ifndef CONSTRAINTMAP_H 2. #define CONSTRAINTMAP_H 3. 4. /* included class definitions: */ 5. #include <QHash> 6. #include <QString> 7. 8. class Constraint; 9. 10. class ConstraintMap : public QHash<QString, Constraint*> { 11. 12. private: 13. Constraint* m_Constraintptr; 14. // Constraint m_ConstraintObj; 15. void addConstraint(Constraint& c); 16. }; 17. 18. #endif // #ifndef CONSTRAINTMAP_H
- Line 8: Forward declaration.
- Line 10: Needs definitions of QHash and QString, but only the declaration of Constraint, because it’s a pointer.
- Line 13: No problem; it’s just a pointer.
- Line 14: Error: incomplete type.
- Line 15: Using forward declaration.
As you can see, inside function parameter lists we can use pointers or references to classes that were only declared, not defined. The pointer-dereferencing and member-accessing operations are performed in an implementation file, shown in Listing 6 (line numbers added). There, we must #include the full definitions of each type it uses.
Listing 6 src/preprocessor/constraintmap.cpp.
1. #include "constraintmap.h" 2. 3. ConstraintMap map; 4. 5. /* redundant but harmless if #ifndef wrapped. */ 6. #include "constraintmap.h" 7. 8. Constraint * constraintP; 9. 10. // Constraint p; 11. #include <constraint.h> 12. Constraint q; 13. 14. void ConstraintMap::addConstraint(Constraint& c) { 15. cout << c.name(); 16.}
- Line 3: Okay; ConstraintMap is already included.
- Line 8: Using forward declaration from constraintmap.h.
- Line 10: Error: incomplete type.
- Line 12: Now it’s a complete type.
- Line 15: The complete type is required here.
To minimize the number of strong dependencies between header files, we try to declare classes instead of including other header files. Here are some guidelines to help decide whether you need a forward declaration, or the full header file #included.
- If ClassA derives from ClassB, the definition of ClassB must be known by the compiler when it processes the definition of ClassA. Therefore, the header file for ClassA must #include the header file for ClassB.
- If the definition of ClassA contains a member that is an object of ClassD, the header file for ClassA must #include the header file for ClassD. If the definition of ClassA contains a function that has a parameter or a return object of ClassD, the header file for ClassA must #include the header file for ClassD.
- If the definition of ClassA contains only non-dereferenced ClassE pointers or references, a forward declaration of ClassE is sufficient in the ClassA header file:
class ClassE;
A class that is declared but not defined is considered an incomplete type. Any attempt to dereference a pointer or define an object of an incomplete type will result in a compiler error.
The implementation file, classa.cpp, for ClassA should #include "classa.h" and also #include the header file for each class used by ClassA (unless that header file has already been included in classa.h). Any pointer dereferencing should be performed in the .cpp file. This technique helps reduce dependencies between classes and improves compilation speed.
A .cpp file should never #include another .cpp file:
- A header file should #include as few other header files as possible, so that it can be included more quickly and with fewer dependencies.
- A header file should always be #ifndef wrapped to prevent it from being included more than once.