Включая #include в заголовочный файл против исходного файла - PullRequest
48 голосов
/ 08 апреля 2010

Мне нравится помещать все мои #include в заголовочный файл, а затем включать только мой заголовок для этого исходного файла в мой исходный файл. Что такое отраслевой стандарт? Есть ли какие-либо недостатки в моем методе?

1 Ответ

64 голосов
/ 08 апреля 2010

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

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

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

На этой странице есть хорошее резюме. (Тиражируется ниже для справки)


Файл заголовка C ++ включает шаблоны #

Большие программные проекты требуют тщательного управления файлами заголовков даже при программировании на C. Когда разработчики переходят на C ++, управление файлами заголовков становится еще более сложным и отнимает много времени. Здесь мы представляем несколько шаблонов включения файла заголовка, которые упростят эту работу.

Правила включения в заголовочный файл

Здесь мы обсудим основные правила включения файла заголовка C ++, необходимые для упрощения управления файлом заголовка.

Заголовочный файл должен быть включен только тогда, когда предварительное объявление не выполнит свою работу. Файл заголовка должен быть спроектирован таким образом, чтобы порядок включения файла заголовка не имел значения. Это достигается за счет того, что x.h является первым заголовочным файлом в x.cpp Механизм включения файла заголовка должен допускать дублирование включений файла заголовка. Следующие разделы объяснят эти правила с помощью примера.

Пример включения файла заголовка

Следующий пример иллюстрирует различные типы зависимостей. Предположим, класс A с кодом, хранящимся в a.cpp и a.h.

a.h

#ifndef _a_h_included_
#define _a_h_included_
#include "abase.h"
#include "b.h"

// Forward Declarations
class C;
class D;

class A : public ABase
{
  B m_b;
  C *m_c;
  D *m_d;

public:
  void SetC(C *c);
  C *GetC() const;

  void ModifyD(D *d);
};
#endif

a.cpp

#include "a.h"
#include "d.h"

void A::SetC(C* c)
{
  m_c = c;
}

C* A::GetC() const
{
  return m_c;
}

void A::ModifyD(D* d)
{
  d->SetX(0);
  d->SetY(0);
  m_d = d;
}

Анализ включений файлов

Позволяет проанализировать включения файла заголовка с точки зрения классов, участвующих в этом примере, то есть ABase, A, B, C и D.

  • Class ABase: ABase является базовым классом, поэтому для завершения объявления класса требуется объявление класса. Компилятору необходимо знать размер ABase, чтобы определить общий размер A. В этом случае abase.h следует явно включить в a.h.
  • Класс B: Класс A содержит класс B по значению, поэтому для завершения объявления класса требуется объявление класса. Компилятору необходимо знать размер B, чтобы определить общий размер A. В этом случае b.h следует явно включить в a.h.
  • Класс C : Class C включен только в качестве ссылки на указатель. Размер или фактическое содержание C не имеют значения для a.h или a.cpp. Таким образом, в a.h была включена только предварительная декларация. Обратите внимание, что c.h не был включен ни в a.h, ни в a.cpp.
  • Класс D : Класс D просто используется в качестве ссылки на указатель в a.h. Таким образом, предварительное заявление достаточно. Но a.cpp использует класс D по существу, поэтому он явно включает d.h.

Ключевые моменты

Заголовочные файлы должны включаться только в том случае, если предварительное объявление не выполнит эту работу. Не включая c.h и d.h других клиентов класса A, никогда не придется беспокоиться о c.h и d.h, если они не используют классы C и D по значению.a.h был включен в качестве первого файла заголовка в a.cpp Это будет гарантировать, что a.h не ожидает, что определенные файлы заголовков будут включены до a.h. Поскольку a.h был включен в качестве первого файла, успешная компиляция a.cpp гарантирует, что a.h не ожидает, что любой другой заголовочный файл будет включен до a.h. Если это будет следовать для всех классов (то есть x.cpp всегда включает x.h в качестве первого заголовка), то не будет никакой зависимости от включения файла заголовка. a.h включает проверку определения препроцессора символа _a_h_included_. Это делает допустимым дублирование включений a.h.

циклическая зависимость

В следующем примере существует циклическая зависимость между классами X и Y. Эта зависимость обрабатывается с помощью предварительных объявлений.

x.h and y.h

/* ====== x.h ====== */
// Forward declaration of Y for cyclic dependency
class Y;

class X 
{
    Y *m_y;
    ...
};

/* ====== y.h ====== */
// Forward declaration of X for cyclic dependency
class X;

class Y 
{
    X *m_x;
    ...
};
...