2009-11-09 3 views
5

C++ заголовковC++ Headers - Лучшая практика, когда в том числе

Если у меня есть a.cpp и Ah, а также ЬН, ч, дк

Должен ли я сделать:

в Ач:

#include "b.h" 
#include "c.h" 
#include "d.h" 

в a.cpp:

#include "A.h" 

или

в a.cpp:

#include "A.h" 
#include "b.h" 
#include "c.h" 
#include "d.h" 

Есть ли проблемы с производительностью? Очевидные преимущества? Что-то плохое об этом?

ответ

0

Предпочитает не включая заголовки других заголовков - это замедляет компиляцию и Leas круговой ссылки

+0

@mgd - Если у меня есть заголовок, содержащий 10 включений, и у источника было 10 различных включений, нужно ли просто переместить включения из заголовка в исходный код, чтобы источник содержал 20? –

+0

См. Более полный ответ alex. Единственное исключение - если у вас есть предварительно скомпилированные заголовки, в этом случае включают все заголовки платформы/рамки/ОС, которые никогда не будут меняться в один файл и включают в себя всюду –

0

Там не будет никаких проблем с производительностью, все это делается во время компиляции, и ваши заголовки должны быть настроены так, что они могут» t быть включенным более одного раза.

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

+0

@Jeff - ah right, возможно, элементы в заголовке могут понадобиться. но не получится ли это, если все заголовки находятся в исходном файле? –

20

Вы должны включать только то, что необходимо для компиляции; добавление ненужных включений повредит ваши времена компиляции, особенно в крупных проектах.

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

Постарайтесь использовать форвардные объявления как можно больше. Если вы используете класс, но файл заголовок имеет дело только с указателями/ссылками на объекты этого класса, то нет необходимости включать определение класса - просто использовать опережающее объявление:

class SomeClass; 
// Can now use pointers/references to SomeClass 
// without needing the full definition 
+0

@Adam - есть ли у вас пример передовых объявлений и когда это целесообразно использовать, а когда нет? –

+1

Большой совет. Я также рекомендовал бы, чтобы файл .c, связанный с .h, включал .h, как первый, всегда. Это делается для того, чтобы все ваши .h-файлы всегда могли компилироваться самостоятельно (вместо того, чтобы полагаться на порядок включения в ваш .c-файл). –

+0

+1 для последней части, я ранее всегда включал заголовочный файл –

3

То, что сказал вам Адам Розенфилд, - это место. Пример, когда вы можете использовать опережающее объявление является:

#ifndef A_H_ 
#define A_H_ 

    #include "D.h" 
    class B; //forward declaration 
    class C; //forward declaration 

    class A 
    { 
    B *m_pb; //you can forward declare this one bacause it's a pointer and the compilier doesn't need to know the size of object B at this point in the code. include B.h in the cpp file. 
    C &m_rc; //you can also forware declare this one for the same reason, except it's a reference. 

    D m_d; //you cannot forward declare this one because the complier need to calc the size of object D. 

    }; 

#endif 
+0

(надеюсь, что строит, я не тестировал его. Дайте мне знать, если у вас есть проблемы с ним). – cchampion

6

Ключа практик здесь имея вокруг каждого foo.h файл охранника, такие как:

#ifndef _FOO_H 
#define _FOO_H 

...rest of the .h file... 
#endif 

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

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

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

Если вы используете классы по стоимости, увы, вам нужны все детали gory класса в некотором .h, который вы включили. Но для некоторых применений с помощью ссылок или указателей достаточно простого class sic;. Например, при прочих равных условиях, если класс a может уйти с указателем экземпляру класса b (т. Е. Члену class b *my_bp;, а не члену class b *my_b;), связь между включенными файлами может быть уменьшена (уменьшая значительную перекомпиляцию) - например b.h может иметь немного больше чем class b; в то время как все окровавленные детали в b_impl.h который входит только заголовки, которые действительно нужно ...

+0

Учитывая, что foo.h все равно будет читаться каждый раз, когда он включен, на самом деле это не так-то. Это было бы no-op, если бы вы сделали: #ifndef _FOO_H #include "foo.h" #endif – Bill

+0

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

3

Ответ: Пусть A.h включают b.h, c.h и d.h только тогда, когда это необходимо для сделать сборку успешной. Эмпирическое правило: при необходимости указывать только столько кода в файле заголовка. Все, что не требуется немедленно в A.h, должно быть включено A.cpp.

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

Используя forward declarations, где это возможно в ваших заголовочных файлах, вы уменьшаете связь исходных файлов и тем самым быстрее выполняете сборку. Такие форвардные декларации могут использоваться во многих других ситуациях, чем вы думаете. Как правило, вам нужно только файл заголовка t.h (который определяет тип T), если вы

  1. Объявите переменную-член типа T (примечание, это не включать объявляя указатель-to T).
  2. Напишите некоторые встроенные функции, которые получают доступ к элементам объекта типа T.

Вы делаете не необходимости включать декларацию T, если ваш файл заголовка только

  1. Объявляет конструкторы/функции, которые принимают ссылки или указатели на T объекта.
  2. Объявляет функции, которые возвращают объект T указателем, ссылкой или значением.
  3. Объявляет переменные-члены, которые являются ссылками или указателями на объект T.

Рассмотрите это объявление класса; которые инклюдников для A, B и C вам действительно нужно включить ?:

class MyClass 
{ 
public: 
    MyClass(const A &a); 

    void set(const B &b); 
    void set(const B *b); 
    B getB(); 
    C getC(); 

private: 
    B *m_b; 
    C m_c; 
}; 

Вам просто нужно включить файл для типа C из-за переменной-члена m_c. Вы также можете удалить это требование, не объявляя свои переменные-члены напрямую, но используя opaque pointer, чтобы скрыть все переменные-члены в частной структуре, чтобы они больше не отображались в файле заголовка.

+0

Вы смешиваете A, B и C с X, Y и Z в своем примере. – Bill

+0

@Bill: Вы правы, я скорректировал пример, чтобы сказать A, B, C сейчас. –

+0

@FrerichRaabe безупречный ответ. +1 для части «Рассуждение:». – ParokshaX