Chapter 9. Compiling and Linking

Table of Contents

Compile-time dependences in C++"
Removing Unnecsessary Inheritance
The PIMPL idiom
Codeblow
Using C and C++ together
Interpositioning
Stupid linker?
Order of linking
Dynamic linking

Everything on project organization issues. The PIMPL and the Firewall compiling idioms. Minimalize dependencies between classes. Compilers, compatibility issues. Static and dinamic linking. How to organize templates.

Compile-time dependences in C++"

Object-orientation is a well-accepted technology to minimalize object 1interactions, ie: run-time dependences between objects. In the same time reducing compile-time dependences is also an important issue for project management purposes.

In C++ when anything in a header file changes, all code that includes the header (either directly or indirectly) must be recompiled.


// x.h: sample header
// version 1

#include <iostream>
#include <ostream>
#include <list>

// none of A, B, C, D, E are templates
// Only A and C have virtual functions

#include "a.h"      // class A
#include "b.h"      // class B
#include "c.h"      // class C
#include "d.h"      // class D
#include "e.h"      // class E

class X : public A, private B
{
public:
        X( const C&);
    B   f(int, char*);
    C   f(int, C);
    C&  g(B);
    E   h(E);
    virtual std::ostream& print(std::ostream&) const;
private:
    std::list<C>    clist_;
    D               d_;
};

inline std::ostream& operator<<( std::ostream& os, const X& x)
{
    return x.print(os);
}

There are too many headers unneccessary included in x.h. That is bad, because every client that includes x.h is also forced to include them. In large projects that could be painfull.

  • Remove <iostream> People automatically include <iostream>, even if input functions never used.

  • Replace <ostream> with <iosfwd> Parameters and return types only need to be forward declared. Because ostream is basic_ostream<char> template, it is not enough to declare:

  • Replace "e.h" with forward declaration of class E

  • Leave "a.h" and "b.h": we need a full declaration of the base classes in case of inheritance. The compiler must know the size of bases, whether functions are virtual or not.

  • Leave "c.h" and "d.h": list<C> and D are private data members of X.


// x.h: sample header
// version 2

#include <iosfwd>
#include <list>

// none of A, B, C, D, E are templates
// Only A and C have virtual functions

#include "a.h"      // class A
#include "b.h"      // class B
#include "c.h"      // class C
#include "d.h"      // class D

class E;

class X : public A, private B
{
public:
        X( const C&);
    B   f(int, char*);
    C   f(int, C);
    C&  g(B);
    E   h(E);
    virtual std::ostream& print(std::ostream&) const;
private:
    std::list<C>    clist_;
    D               d_;
};

inline std::ostream& operator<<( std::ostream& os, const X& x)
{
    return x.print(os);
}

C++ lets us encapsulate the private parts of a class from unathorized access. It can take a little more work to encapsulate dependencies on a class's privates.

Originally it was impl_, the Hungarian notation of pointer made it pimpl_. Officially it is called compilation firewall.


// file x.h
class X
{
    // public and protected members
private:
    // private members:
    // whenever these change,
    // all client code must be recompiled
};

We replace that with:


// file x.h
class X
{
    // public and protected members
private:
    // pointer to forward declared class
    class XImpl *pimpl_;  // opaque pointer
};


// file x.cpp
struct X    // not neccessary to declare as "class"
{
    // private members; fully hidden
    // can be changed at without
    // recompiling clients
};

Advantages:

  • Types mentioned only in a class's implementation need no longer be defined for client code, wich eliminate extra #includes and improve compile speed.

  • A class's implementation can be changed - private members can be added or removed - without replacing client code.

Costs:

  • Each construction/destruction must allocate/deallocate memory.

  • Each access of a hidden member can require at least one extra indirection. Sometimes from the hidden part we must access members in visible part: that we need another extra indirection.


// x.h: sample header
// version 3

#include <iosfwd>
#include "a.h"      // class A
#include "b.h"      // class B

class C;
class E;

class X : public A, private B
{
public:
        X( const C&);
    B   f(int, char*);
    C   f(int, C);
    C&  g(B);
    E   h(E);
    virtual std::ostream& print(std::ostream&) const;
private:
    // opaque pointer to forward-declared class   
    class XImpl *pimpl_;
};

inline std::ostream& operator<<( std::ostream& os, const X& x)
{
    return x.print(os);
}


// file x.cpp

#include "x.h"
#include "c.h"      // class C
#include "d.h"      // class D

struct XImpl
{
    std::list<C>    clist_;
    D               d_;
};