C.2. The Preprocessor: For #including Files

[ fromfile: preprocessor.xml id: preprocessor ]

In C++, code reuse is indicated by the presence of a preprocessor directive, #include, at the top of source code files. You #include header files that contain things like class or namespace definitions, const definitions, function prototypes, and so forth. These files are literally included in your own files before the compiler begins to translate your code.

The compiler reports an error if it sees any identifier defined more than once. It will tolerate repeated declarations but not repeated definitions.[124] To prevent repeated definitions, be careful to use an #ifndef wrapper around each header file. This tells the Preprocessor to skip the contents if it has already seen them. Let's examine the following class definition in Example C.2

Example C.2. src/preprocessor/constraintmap.h

#ifndef CONSTRAINTMAP_H
#define CONSTRAINTMAP_H

#include <QHash>
#include <QString>

class Constraint;                                           1

class ConstraintMap : public QHash<QString, Constraint*> {  2

private:
    Constraint* m_Constraintptr;                            3
    Constraint m_ConstraintObj;                             4
    void addConstraint(Constraint& c);                      5
};
#endif        //  #ifndef CONSTRAINTMAP_H

1

Forward declaration.

2

Needs definitions of QHash and QString, but only the declaration of Constraint, because it's a pointer.

3

No problem, it's just a pointer.

4

Error: incomplete type.

5

Using forward declaration.


As you can see, inside function parameter lists, you can use pointers or references to classes which were only declared, not defined. The pointer dereferencing and member accessing operations are performed in the implementation file shown in Example C.3. There, you must #include the full definitions of each type it uses.

Example C.3. src/preprocessor/constraintmap.cpp

#include "constraintmap.h"

ConstraintMap map;              1
#include "constraintmap.h"      2

Constraint* constraintP;        3

Constraint p;                   4
#include <constraint.h>
Constraint q;                   5

void ConstraintMap::addConstraint(Constraint& c) {
    cout << c.name();           6
}

1

Okay, ConstraintMap is already included.

2

Redundant but harmless if #ifndef wrapped.

3

Using forward declaration from constraintmap.h.

4

Error: incomplete type.

5

Now it is a complete type.

6

Complete type required here.


To minimize the number of "strong dependencies" between header files, you should declare classes instead of #including header files whenever posible. Here are some guidelines to help decide whether you need a forward declaration, or the full header file #included.

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 results in a compiler error.[125]

The implementation file, classa.cpp, for ClassA should #include "classa.h" and also #include the header file for each class that is 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 helps reduce dependencies between classes and improves compilation speed.



[124] Section 20.1 discusses the difference between declaration and definition.

[125] The actual error message may not always be clear, and with QObjects, it might come from the MOC-generated code, rather than your own code.