Forward references/declarations & Compilation time

Español


Everybody knows what a forward reference / forward declaration in C/C++ is… Well, a few months ago, I didn’t (at least not in the way I’ll try to explain how to use it today)

A few definitions of that can be found on the Internet, here by hp, here by wikipedia, short snippets on stack overflow here, and so on.

The main idea is to 1) declare something incompletely to 2) use it before a full definition. (Being 1 forward declarations and 2 forward references)

In this case, I’m particularly interested in forward declaration/reference of classes in C++ and a resulting gain of compilation time (they are also used to solve the problem of cyclical dependency of two entities, here a code snippet to illustrate it)

This is a simple example to show the potential reduction of compilation time, based on the usage of forward declaration/reference.

A.h

#include "b.h"

class A
{
public:
    A();

private:
    B* mpB;
};

A.cpp

#include "a.h"

A::A() :
    mpB(0)
{
}

B.h

class B
{
public:
    B();
};

B.cpp

#include "b.h"

B::B()
{
}

C.h

#include "a.h"

class C
{
public:
    C();
    void doSomething();
private:
    A myFullObject;
};

C.cpp

#include "c.h"

C::C()
{
}

void C::doSomething() {}

D.h

#include "a.h"

class D
{
public:
    D();
    void doSomethingWithAFullAObject(A aObject);
};

D.cpp

#include "d.h"

D::D()
{
}

A clean build, give us that output (I’m using QtCreator for this example, nice IDE):

g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o main.o ../FRCT/main.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o a.o ../FRCT/a.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o b.o ../FRCT/b.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o c.o ../FRCT/c.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o d.o ../FRCT/d.cpp

A new build without any modification:

make: Nothing to be done for `first'.

Ok, what if I modify something in B:

class B
{
public:
    B();
private:
    int mNewProp;
};
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o a.o ../FRCT/a.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o b.o ../FRCT/b.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o c.o ../FRCT/c.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o d.o ../FRCT/d.cpp

Even when C & D where completely unrelated with B, they both where recompiled, why?

Because

  1. They both need to include A.
  2. A, instead of a forward declaration on its header, it’s making an include.
  3. When B changed, A’s header changed as well, so C and D were recompiled.

Try again using forward declaration/reference:

A.h modified (remember to add the “include “b.h” on the .cpp)

class B;

class A
{
public:
    A();

private:
    B* mpB;
};

Clean & build:

g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o main.o ../FRCT/main.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o a.o ../FRCT/a.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o b.o ../FRCT/b.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o c.o ../FRCT/c.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o d.o ../FRCT/d.cpp

Re-edit B & build:

g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o a.o ../FRCT/a.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o b.o ../FRCT/b.cpp

That avoided the unnecessary compilation of both C & D that weren’t modified and didn’t had any dependency to B.

Picture that simple example but, on a 200, 700, 1200 files/headers project…

So, whenever you where able to use forward references/declarations, use them!
Back to Top




Todo el mundo sabe lo que una forward reference / forward declaration es en C/C++… Bueno, hace unos meses, yo no (por lo menos no de la manera en que voy a tratar de explicar cómo usar hoy)

Una definición en español puede ser ésta y sino alguna en inglés por hp y wikipedia con algunos ejemplos más acá en stackoverlow.

La idea principal es 1) declarar algo de manera incompleta para 2) usarlo antes de una definición completa de ello. (Suendo 1 forward declaration y 2 forward reference).

En este caso, estoy particularmente intereado en forward declaration/reference de clases en C++ y la resultante ganancia en tiempo de compilación (también se utilizan para resolver el problema de dependencias cíclicas, como explica más arriba el link en español).

A continuación, un ejemplo simple de la reducción potencial de tiempo de compilación, basado en el uso de las forward declarations/references

A.h

#include "b.h"

class A
{
public:
    A();

private:
    B* mpB;
};

A.cpp

#include "a.h"

A::A() :
    mpB(0)
{
}

B.h

class B
{
public:
    B();
};

B.cpp

#include "b.h"

B::B()
{
}

C.h

#include "a.h"

class C
{
public:
    C();
    void hacerAlgo();
private:
    A miObjetoACompleto;
};

C.cpp

#include "c.h"

C::C()
{
}

void C::hacerAlgo() {}

D.h

#include "a.h"

class D
{
public:
    D();
    void hacerAlgoConUnObjetoA(A aObjeto);
};

D.cpp

#include "d.h"

D::D()
{
}

Al compilar (con un clean previo), se obtiene el siguiente output (Para el ejemplo estoy usando QtCreator, un bonito IDE):

g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o main.o ../FRCT/main.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o a.o ../FRCT/a.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o b.o ../FRCT/b.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o c.o ../FRCT/c.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o d.o ../FRCT/d.cpp

Otro build, sin modificaciones:

make: Nothing to be done for `first'.

Bien, y si modifico algo en B?

class B
{
public:
    B();
private:
    int mNuevaPropiedad;
};
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o a.o ../FRCT/a.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o b.o ../FRCT/b.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o c.o ../FRCT/c.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o d.o ../FRCT/d.cpp

Aún cuando C & D no tienen ninguna relación con B, ambas fueron compiladas nuevamente, ¿por qué?

Porque

  1. Ambas necesitan incluir a A
  2. A, en lugar de hacer una forward declaration en su header, está haciendo un include.
  3. Cuando B cambia, el header de A cambia también, y luego C y D cambian, por cambiar A.

Tratando nuevamente usando forward declaration/reference:

A.h modificado (recordar agregar la línea “include b.h” en el .cpp)

class B;

class A
{
public:
    A();

private:
    B* mpB;
};

Clean & build:

g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o main.o ../FRCT/main.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o a.o ../FRCT/a.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o b.o ../FRCT/b.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o c.o ../FRCT/c.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o d.o ../FRCT/d.cpp

Re-editar B y compilar:

g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o a.o ../FRCT/a.cpp
g++ -c -pipe -O2 -Wall -W -I../FRCT -I../FRCT -I. -o b.o ../FRCT/b.cpp

Se evitó la compilación innecesaria de ambos C y D, que no fueron modificados ni tenían dependencia alguna con B.

Imaginá este simple ejemplo pero, en un proyecto de 200, 700 1200 archivos/headers…

Así que, cuando sea posible utilizar forward references/declarations, a usarlas!
Inicio

Leave a Reply (Deja una respuesta)