C ++ включает соглашения о заголовках - PullRequest
8 голосов
/ 31 марта 2010

Предположим, у меня есть файл X.h, который определяет класс X, методы которого реализованы в X.cc. Файл X.h включает в себя файл Y.h, потому что ему нужен Y для определения класса X. В X.cc мы можем ссылаться к Y, потому что X.h уже включил Y.h. Должен ли я по-прежнему включать Y.h в X.cc?

Я понимаю, что мне это не нужно, и я могу рассчитывать на защиту от заголовков, чтобы предотвратить множественные включения. Но с одной стороны, включение Y.h делает X.cc немного более независимым от X.h (не может быть полностью независимый конечно). Какова принятая практика?

Другой пример: включение <iostream> в файлы .h и .cc. Я вижу, что некоторые люди делают это а некоторые нет.

Ответы [ 7 ]

10 голосов
/ 31 марта 2010

Будь минимальным. В заголовках предпочитайте предварительные объявления полным определениям. Например, используйте iosfwd вместо ostream.

Тем не менее, X.h и X.cc представляют одну и ту же логическую единицу. Если ваша зависимость от Y.h когда-либо изменилась (например, превратилась в предварительное объявление), вы все равно изменили бы класс. Таким образом, вы можете переместиться #include "Y.h" в X.cc оправданно.

Другими словами, X.cc и X.h идут рука об руку. X.cc может достоверно предположить, что находится в X.h. Поэтому нет необходимости что-то включать, если X.h делает.

Зависимости, в которых вы все равно «включаете», возникают с ресурсами , отличными от , чем ваши собственные. Например, если вам нужен Z.h, вы включите его, даже если Y.h сделает. X.h не может надежно принять содержимое Y.h, потому что X.h не совпадает с Y.h, он использует его.

6 голосов
/ 31 марта 2010

Я бы предложил включить заголовок include для Y в X.cc, даже если он кажется избыточным. Это дает вам преимущество в том, что вы очень четко выражаете свои зависимости.

Как примечание, вы всегда должны #include связанный заголовок для файла cpp как первый # include'd файл. (Первым включением в X.cpp должно быть X.h). Это гарантирует, что заголовок включает в себя надлежащие файлы для разрешения своих собственных зависимостей, в противном случае вы можете непреднамеренно полагаться на порядок включений в исходном файле.

3 голосов
/ 31 марта 2010

Я не уверен, почему это было бы хорошей практикой.

С другой стороны, не , включая ненужные файлы в X.h, я считаю очень хорошей практикой.

Например, в следующем сценарии:

X.h

#include "Y.h"

class X
{
private:
    Y * m_pY;

public:
    X();
    ~X();
}

Было бы достаточно форвард объявить Y. Клиенты класса X не должны платить за включение файла заголовка для Y:

X.h

class Y; // include Y.h in X.cc instead

class X
{
private:
    Y * m_pY;

public:
    X();
    ~X();
}

Это возможно в заголовочном файле, потому что объявление класса X не требует специальных сведений о Y (например, размер экземпляра); только то, что Y является классом. Кроме того, клиенты класса X никогда не имеют дело с типом Y, поскольку он не является частью открытого интерфейса X.

В больших проектах избегание ненужных директив include в заголовочных файлах может значительно сократить время сборки.

Он также может не загрязнять пространство имен пользователей вашего класса символами из ваших классов / классов реализации.

1 голос
/ 31 марта 2010

Я думаю, ты должен. По мере роста вашего кода и некоторых изменений в реализации некоторые из них могут быть удалены из файла заголовка, который включен другим файлом заголовка, который включен в файл заголовка ... Может быть очень неприятно выяснить, почему что-то, что не имеет ничего общего с изменениями, внесенными вами (или кем-то еще), больше не работает.

1 голос
/ 31 марта 2010

Это на самом деле не имеет большого значения [по крайней мере, если у вас нет гигантского проекта], но я бы, вероятно, склонялся к его включению.

0 голосов
/ 31 марта 2010

#include операторы в заголовочных файлах могут увеличить время компиляции. Для игрушечных приложений или больших систем с одной зависимостью, как вы описали, это не будет проблемой. Но по мере роста вашего приложения время компиляции будет расти. Одно из решений в MSVC состоит в том, чтобы использовать предварительно скомпилированные заголовки (которые приносят свой собственный набор головной боли) или исключить все операторы #include в заголовках. Последнее - мой подход по умолчанию, и я сначала пытаюсь использовать предварительные объявления, где это необходимо:

гг.ч .:

class Y {public: int foo_; };

x.h:

class Y;
class X { public: Y* y_; };

main.cc:

#include "y.h"
#include "x.h"

int main()
{
  X x;
  return 0;
}

Если использование forward-объявлений невозможно, просто помните, что #include не делает ничего, кроме как доводит содержимое указанного файла до этой точки, и это можно сделать так же легко из файла cc, как из заголовка:

гг.ч .:

class Y {public: int foo_; };

x.h:

class X {public: Y y_; }; // note this declaration requires a concrete type for Y

main.cc:

#include "y.h"
#include "x.h"

int main()
{
  X x;
  return 0;
}
0 голосов
/ 31 марта 2010

Из вашего примера я бы включил X.h в X.cc только для уменьшения количества включений. Да, в более общем случае, когда у вас есть A.cc, включая Xh, и в качестве побочного эффекта - возможность ссылаться на вещи в Yh, когда вы можете удалить Xh, вы обнаружите, что вам нужно вручную добавить Yh, потому что вы не добавляли его раньше , По крайней мере, компилятор будет на вашем деле и напомнит вам.

В случае <iostream> он вам нужен, когда он вам нужен, будь то в файле заголовка или в модуле.

...