Плюсы и минусы размещения всего кода в заголовочных файлах на C ++? - PullRequest
45 голосов
/ 11 октября 2008

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

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

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

Ваши мысли?

Ответы [ 17 ]

2 голосов
/ 11 октября 2008

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

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

// 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.
}

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

2 голосов
/ 11 октября 2008

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

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

1 голос
/ 11 октября 2008

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

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

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

0 голосов
/ 16 апреля 2014

Важная философия объектно-ориентированного программирования заключается в том, чтобы скрывать данные, приводя к инкапсулированным классам с реализацией, скрытой от пользователей. Это прежде всего для обеспечения уровня абстракции, где пользователи класса в основном используют общедоступные функции-члены для конкретных, а также статических типов. Затем разработчик класса может модифицировать фактические реализации при условии, что реализации не будут представлены пользователям. Даже если реализация является частной и объявлена ​​в заголовочном файле, изменение реализации потребует перекомпиляции всей зависимой кодовой базы. Принимая во внимание, что если реализация (определение функций-членов) находится в исходном коде (файл без заголовка), то библиотека изменяется, и зависимая кодовая база должна повторно связываться с пересмотренной версией библиотеки. Если эта библиотека динамически связана с библиотекой, такой как разделяемая библиотека, то при сохранении сигнатуры функции (интерфейса) и изменении реализации не требуется также повторное связывание. Преимущество? Конечно.

0 голосов
/ 19 ноября 2008

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

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

0 голосов
/ 30 октября 2008

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

0 голосов
/ 11 октября 2008

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

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

В файлах MY kludged размещение таких элементов в верхней части файла cpp облегчает их поиск.

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

...