2.3. Снижение числа #include с помощью предварительного объявления классов

2.3. Снижение числа #include с помощью предварительного объявления классов

Проблема

Имеется заголовочный файл, который ссылается на классы из других заголовочных файлов, и требуется снизить зависимости компиляции (и, возможно, время).

Решение

Чтобы избежать ненужных зависимостей при компиляции, везде, где только возможно, используйте предварительное объявление классов. Пример 2.4. является коротким примером предварительного объявления класса.

Пример 2.4. Предварительное объявление класса

// myheader.h

#ifndef MYHEADER_H__

#define MYHEADER_H__

class A; // заголовок для А включать не требуется

class В {

public:

 void f(const A& a);

 // ...

private:

 A* a_;

};

#endif

Где-то в другом месте имеется заголовочный файл и, вероятно, файл реализации, который объявляет и определяет класс А, но в файле myheader.h подробности класса А меня не волнуют: все, что мне требуется знать, — это то, что А — это класс.

Обсуждение

Предварительное объявление класса — это способ игнорировать подробности, о которых не требуется беспокоиться. В примере 2.4 myheader.h не должен знать о классе А ничего, кроме того, что он существует и что это класс.

Рассмотрим, что случится, если с помощью #include включить заголовочный файл для А, или, что более реально, заголовочный файл для полудюжины или более классов, присутствующих в реальном заголовочном файле. Тогда файл реализации (myheader.cpp) будет включать заголовочный файл myheader.h, так как он содержит все объявления. До сих пор все было хорошо. Но если вы измените один из заголовочных файлов, включаемых в myheader.h (или один из заголовочных файлов, включаемых одним из этих файлов), то потребуется перекомпилировать все файлы реализации, включающие myheader.h.

Создайте предварительное объявление класса, и эти зависимости компиляции исчезнут. Использование предварительного объявления просто создает имя, на которое можно ссылаться далее в заголовочном файле. Компоновщик должен будет сам найти в файлах реализаций подходящее определение.

К несчастью, использовать предварительное объявление можно не всегда. Класс В в примере 2.4 использует только указатели или ссылки на A, так что ему достаточно только предварительного объявления. Однако если бы в определении класса В я использовал функцию-член (метод) или переменную А или если бы создавал объект типа А, а не только указатель или ссылку на него, то предварительного объявления окажется недостаточно. Причиной этого является то, что файлы, включающие myheader.h, должны знать размер В, и если A является членом В, то компилятор, чтобы определить размер В, должен знать размер А. Указатель или ссылка на что-либо всегда имеют один и тот же размер, так что в случае использования указателей или ссылок подробности об А компилятор не интересуют, и, следовательно, заголовочный файл не требуется

Неудивительно, что если включить в myheader.h какое-либо определение, использующее членов A, то потребуется включить через #include заголовок для А. Это требуется для того, чтобы компилятор мог проверить сигнатуру используемой функции-члена А или тип переменной-члена А. Вот иллюстрация кода, требующего #include.

#include "a.h"

class B {

public:

 void f(const A& a) {

  foo_ = a.getVal(); // требуется знать, допустимо ли a.getVal

 }

}

// ...

В общем случае используйте предварительное объявление тогда, когда это позволяет снизить количество #include, что отражается на времени компиляции.