Effective C++ item 31: Minimize Compilation Dependencies Between Files

If you want your build to be lightening fast, you need to think about compilation dependencies and try to minimize them as much as possible. This way, you need to re-compile as less files as possible when you have to change code.

If forward declaration comes into your mind, you are on the right track. But there are several things need to be mentioned.

  • Never forward declare standard library classes. Unless you look into details of standard library, it’s very hard to tell if a “class” is actually a class, a typedef or a template class. Depending on these differences, the way you forward delcare things are different. A recommended way is always to include standard headers, never forward declare them. If standard library provides forward declaration headers like iosfwd, feel free to include that.
  • If you are libary provider, considring to provide foofwd.h headers for your clients to use just for declaration. Have definition of those classes separate in foo.h. Of course keep them in sync.

When write definitions, make sure

  • Avoid using objects when object references and pointers will do. You may define references and pointers to a type with only a declaration for the type. Defining objects of a type necessitates the presence of the type’s definition.
  • Depend on class declarations instead of class definitions whenever you can. Note that you never need a class definition to declare a function using that class, not even if the function passes or returns the class type by value:
    1
    2
    3
    4
    #include "datefwd.h"            // header file declaring (but not
    // defining) class Date
    Date today(); // perfectly fine
    void clearAppointments(Date d); // perfectly fine

Two techniques are going to greatly help us to achieve minimizing compilation dependencies. They are handle class(pimpl idiom) and interface class.
Let’s use a example to illustrate both techniques. Considering writing following class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <string>
#include "date.h"
#include "address.h"
class Person {
public:
Person(const std::string& name, const Date& birthday,
const Address& addr);
std::string name() const;
std::string birthDate() const;
std::string address() const;
...
private:
std::string theName; // has to include <string>
Date theBirthDate; // has to include "date.h"
Address theAddress; // has to include "address.h"
};

You need to include three header files if you write it naively.

If you use handle class, or pimpl idiom, this class will look like following

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <string>
class PersonImpl; // forward declaration
class Date; // forward declaration
class Address; // forward declaration
class Person {
public:
Person(const std::string& name, const Date& birthday,
const Address& addr);
std::string name() const;
std::string birthDate() const;
std::string address() const;
...
private:
PersonImpl* pImpl;
};

Essencially you dedicated all implementation details to PersonImpl class, so you can forward declare everything(except standard library) which class function needs, even if they are pass-by-value.

Noe let’s look at how to use interface class to achieve similar thing.

1
2
3
4
5
6
7
8
9
10
11
12
#include <string>
class Date; // forward declaration
class Address; // forward declaration
class Person {
public:
virtual ~Person();
static Person* create(const std::string& name, const Date& birthday, const Address& addr); // factory methond
virtual std::string name() const = 0;
virtual Date birthDate() const = 0;
virtual Address address() const = 0;
...
};

This achieved the same thing handle class gives us which is the ability to forward declare all dependencies.

Reference:
“Effective C++” Third Edition by Scott Meyers.