Профессионалы и Недостатки помещения всего кода в Заголовочных файлах в C++?

Можно структурировать программу C++ так, чтобы (почти) весь код находился в Заголовочных файлах. Это по существу похоже на C# или программу Java. Однако Вам действительно нужен по крайней мере один .cpp файл для получения по запросу во всех заголовочных файлах при компиляции. Теперь я знаю, что некоторые люди абсолютно терпеть не могли бы эту идею. Но я не нашел убедительных оборотных сторон выполнения этого. Я могу перечислить некоторые преимущества:

[1] Более быстрое время компиляции. Все заголовочные файлы только анализируются однажды, потому что существует только один .cpp файл. Кроме того, один заголовочный файл не может быть включен несколько раз, иначе Вы получите повреждение сборки. Существуют другие способы достигнуть более быстрых компиляций при использовании альтернативного подхода, но это настолько просто.

[2] Это избегает круговых зависимостей путем создания их абсолютно ясными. Если ClassA в ClassA.h имеет круговую зависимость от ClassB в ClassB.h, Я должен поместить ссылку вперед, и она терпит. (Обратите внимание, что это непохоже на C# и Java, где компилятор автоматически разрешает круговые зависимости. Это поощряет плохо кодировать методы IMO). Снова, можно избежать круговых зависимостей, если код был в .cpp файлы, но в реальном проекте, .cpp файлы имеют тенденцию включать случайные заголовки, пока Вы не можете выяснить, кто зависит от кого.

Ваши мысли?

45
задан Keith Pinson 16 August 2012 в 16:35
поделиться

16 ответов

Причина [1] Более быстрое время компиляции

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

, Возможно, необходимо сломать проект в более логических источниках/заголовках: для модификации в реализации класса A не должна быть нужна перекомпиляция реализаций класса B, C, D, E, и т.д.

Причина [2] Это избегает круговых зависимостей

Круговые зависимости в коде?

Извините, но у меня должен все же быть этот вид проблемы, являющейся настоящей проблемой: Скажем, A зависит от B, и B зависит от A:

struct A
{
   B * b ;
   void doSomethingWithB() ;
} ;

struct B
{
   A * a ;
   void doSomethingWithA() ;
} ;

void A::doSomethingWithB() { /* etc. */ }
void B::doSomethingWithA() { /* etc. */ }

А хороший способ разрешить проблему состоял бы в том, чтобы сломать этот источник по крайней мере в один источник/заголовок в классе (в некотором роде подобный Java путь, но с одним источником и одним заголовком в классе):

// A.hpp

struct B ;

struct A
{
   B * b ;
   void doSomethingWithB() ;
} ;

.

// B.hpp

struct A ;

struct B
{
   A * a ;
   void doSomethingWithA() ;
} ;

.

// A.cpp
#include "A.hpp"
#include "B.hpp"

void A::doSomethingWithB() { /* etc. */ }

.

// B.cpp
#include "B.hpp"
#include "A.hpp"

void B::doSomethingWithA() { /* etc. */ }

Таким образом, без проблем зависимости, и все еще быстрое время компиляции.

я пропускал что-то?

При работе над "реальными" проектами

в реальном проекте, cpp файлы имеют тенденцию включать случайные заголовки, пока Вы не можете выяснить, кто зависит от кого

, Конечно. Но тогда если у Вас есть время для реорганизации тех файлов для создания "одного CPP" решение, тогда у Вас есть время для очистки тех заголовков. Мои правила для заголовков:

  • ломают заголовок для создания их максимально модульными,
  • Никогда не включают заголовки, в которых Вы не нуждаетесь
  • при необходимости в в символе передайте - объявляют его
  • , только если неудавшееся вышеупомянутое, включайте заголовок

Так или иначе, все заголовки должны быть автономной системой, что означает:

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

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

действительно ли время компиляции проблема? Тогда...

Должен время компиляции быть действительно проблемой, я рассмотрел бы также:

  • Используя предварительно скомпилированные заголовки (это довольно полезно для STL и ПОВЫШЕНИЯ)
  • Уменьшение, связывающееся через идиому PImpl, как объяснено в http://en.wikipedia.org/wiki/Opaque_pointer
  • , сеть Use совместно использовала компиляцию

Заключение

, Что Вы делаете, не помещает все в заголовки.

Вы в основном включаете все свои файлы в один и только один заключительный источник.

, Возможно, Вы побеждаете с точки зрения полной компиляции проекта.

, Но при компиляции для одного небольшого изменения, Вы будете всегда проигрывать.

При кодировании, я знаю, что компилирую часто небольшие изменения (если только иметь компилятор, проверяют мой код), и затем одно заключительное время, сделайте полное изменение проекта.

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

33
ответ дан Adam Bellaire 26 November 2019 в 21:01
поделиться

При использовании шаблонных классов необходимо поместить целую реализацию в заголовок так или иначе...

Компиляция целого проекта сразу (через единственную основу .cpp файл) должна позволить что-то как "Целая Оптимизация Программы" или "Оптимизация перекрестного Модуля", которая только доступна в нескольких усовершенствованных компиляторах. Это не действительно возможно со стандартным компилятором, если Вы предварительно компилируете все свои .cpp файлы в объектные файлы, затем связываясь.

0
ответ дан 26 November 2019 в 21:01
поделиться

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

0
ответ дан Greg Rogers 26 November 2019 в 21:01
поделиться

еще менее прозрачные клуджи static-or-global-variable, возможно, un-debuggable.

, например, подсчет общего количества повторений для анализа.

В МОИХ kludged файлах, помещая такие объекты наверху cpp файла делает их легкими найти.

, "возможно, un-debuggable", я подразумеваю, что обычно буду помещать такое глобальное в окно WATCH. Так как это всегда в объеме - окно WATCH, может всегда добираться до него, неважно, где счетчик команд, оказывается, прямо сейчас. Путем помещения таких переменных вне {} наверху заголовочного файла Вы позволили бы всему нисходящему коду "видеть" их. Путем вставления их {}, бесцеремонно я думал бы, что отладчик больше не будет рассматривать их "в объеме", если счетчик команд будет вне {}. Принимая во внимание, что с kludge-global-at-Cpp-top, даже при том, что это могло бы быть глобально вплоть до разоблачения в Вашем link-map-pdb-etc без оператора экстерна, который другие файлы Cpp не могут получить к нему, избежав случайной связи.

0
ответ дан pngaz 26 November 2019 в 21:01
поделиться

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

, Например, при попытке использовать различные библиотеки Open Source, они могут быть установлены использовать разные подходы для соединения с Вашей программой - некоторые могут использовать динамично загруженный код библиотеки операционной системы, другие установлен быть статически связанным; некоторые могут быть установлены использовать многопоточность, в то время как другие не. И это может быть подавляющая задача для программиста - особенно с ограничением времени - чтобы попытаться уладить эти несовместимые approches.

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

1
ответ дан Stefan Rådström 26 November 2019 в 21:01
поделиться

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

исходный вопрос определил, что был только когда-либо единственный cpp в проекте, но дело не в этом при создании компонента, предназначенного для допускающей повторное использование библиотеки.

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

2
ответ дан pk. 26 November 2019 в 21:01
поделиться

Мне нравится думать о разделении.h и .cpp файлов с точки зрения интерфейсов и реализаций..h файлы содержат описания интерфейса к еще одному, классы и .cpp файлы содержат реализации. Иногда существуют практические проблемы или ясность, которые предотвращают абсолютно чистое разделение, но это - то, где я запускаю. Например, маленькое средство доступа функционирует, я обычно кодирую встроенный в объявлении класса для ясности. Большие функции кодируются в .cpp файле

В любом случае, не позволяйте времени компиляции продиктовать, как Вы структурируете свою программу. Лучше для имения программы это читаемо и удобно в сопровождении по тому, которое компилирует за 1,5 минуты вместо 2 минут.

2
ответ дан mxg 26 November 2019 в 21:01
поделиться

Я полагаю, что, если Вы не используете предварительно скомпилированные заголовки MSVC, и Вы используете Make-файл, или другая основанная на зависимости система сборки, имея отдельные исходные файлы должна скомпилировать быстрее при создании многократно. С тех пор моя разработка является почти всегда повторяющейся, я забочусь больше о том, как быстро она может перекомпилировать изменения, которые я внес в файле x.cpp, чем в двадцати других исходных файлах, которые я не изменил. Кроме того, я вношу изменения намного более часто в исходные файлы, чем я делаю к API, таким образом, они изменяются менее часто.

Относительно, круговые зависимости. Я послушал бы совет paercebal один шаг дальше. У него было два класса, которые имели указатели друг на друга. Вместо этого я сталкиваюсь со случаем более часто, где один класс требует другого класса. Когда это происходит, я включаю заголовочный файл для зависимости в заголовочном файле другого класса. Пример:

// foo.hpp
#ifndef __FOO_HPP__
#define __FOO_HPP__

struct foo
{
   int data ;
} ;

#endif // __FOO_HPP__

.

// bar.hpp
#ifndef __BAR_HPP__
#define __BAR_HPP__

#include "foo.hpp"

struct bar
{
   foo f ;
   void doSomethingWithFoo() ;
} ;
#endif // __BAR_HPP__

.

// bar.cpp
#include "bar.hpp"

void bar::doSomethingWithFoo()
{
  // Initialize f
  f.data = 0;
  // etc.
}

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

2
ответ дан terson 26 November 2019 в 21:01
поделиться

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

C++ разработан для h файлов, имеющих объявления и cpp файлы, имеющие реализации. Компиляторы создаются вокруг этого дизайна.

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

3
ответ дан Paul Nathan 26 November 2019 в 21:01
поделиться

Одна вещь Вы бросаете это, мне было бы нелегко жить без, анонимные пространства имен.

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

3
ответ дан user21714 26 November 2019 в 21:01
поделиться

Вы могли бы хотеть проверить Ленивый C++ . Это позволяет Вам помещать все в единственный файл, и затем это работает до компиляции и разделяет код на.h и .cpp файлы. Это могло бы предложить Вам лучший из обоих миров.

Медленное время компиляции обычно происходит из-за чрезмерной связи в системе, записанной в C++. Возможно, необходимо разделить код на подсистемы с внешними интерфейсами. Эти модули могли быть скомпилированы в отдельных проектах. Таким образом, можно минимизировать зависимость между различными модулями системы.

3
ответ дан Chris de Vries 26 November 2019 в 21:01
поделиться

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

9
ответ дан Chris Jester-Young 26 November 2019 в 21:01
поделиться

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

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

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

Однако я разработал решение, где у меня есть свое объявление класса в.h файле, всем шаблоне кода и встроенном коде в .inl файле и всей реализации не шаблон/встроенный код в моем .cpp файле. .inl файл является #included у основания моего.h файла. Это содержит вещи в чистоте и последовательный.

11
ответ дан 26 November 2019 в 21:01
поделиться

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

, Но...

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

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

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

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

пз: О C# или Java, необходимо иметь в виду, что эти языки не делают то, что Вы говорите. Они на самом деле компилируют файлы независимо (как cpp файлы), и хранит интерфейс глобально для каждого файла. Эти интерфейсы (и любые другие связанные интерфейсы) тогда используются для соединения целого проекта, вот почему они в состоянии обработать циклические ссылки. Поскольку C++ делает только одну передачу компиляции на файл, это не в состоянии глобально сохранить интерфейсы. Вот почему Вы обязаны писать им explicitely в заголовочных файлах.

16
ответ дан Vincent Robert 26 November 2019 в 21:01
поделиться

Я не соглашаюсь с точкой 1.

Да, существует только один .cpp, и созданное время с нуля быстрее. Но, Вы редко создаете с нуля. Вы вносите небольшие изменения, и это должно было бы перекомпилировать целый проект каждый раз.

я предпочитаю делать его наоборот:

  • сохраняют общие объявления в.h файлах
  • , сохраняют определение для классов, которые только используются в одном месте в .cpp файлах

Так, некоторые мои .cpp файлы начинают быть похожими на Java или код C#;)

, Но, 'материал хранения в.h' подход хорош при разработке системы из-за точки 2. Вы сделали. Я обычно делаю это, в то время как я создаю иерархию классов и позже когда архитектура кода становится стабильной, я перемещаю код в .cpp файлы.

25
ответ дан Milan Babuškov 26 November 2019 в 21:01
поделиться

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

3
ответ дан Don Neufeld 26 November 2019 в 21:01
поделиться
Другие вопросы по тегам:

Похожие вопросы: