Несколько классов в файле заголовка против одного файла заголовка на класс

по любой причине, наша компания имеет директиву кодирвоания которая заявляет:

Each class shall have it's own header and implementation file.

Итак, если мы написали класс с именем MyString нам понадобится связанный MyStringh.h и mystring совсем.cxx.

кто-нибудь еще это сделать? Кто-нибудь видел какие-либо последствия компиляции производительности в результате? Компилируется ли 5000 классов в 10000 файлах так же быстро, как 5000 классов в 2500 файлах? Если нет, есть разница заметно?

[мы кодируем C++ и используем GCC 3.4.4 в качестве нашего повседневного компилятора]

12 ответов


термин здесь ЕП и вы действительно хотите (если это возможно) иметь один класс на единицу перевода, т. е. одну реализацию класса на .cpp файл, с соответствующим .H-файл с тем же именем.

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

также вы найдете много ошибок / диагностика сообщается через имя файла ("ошибка в Myclass.cpp, строка 22"), и это помогает, если есть взаимно однозначное соответствие между файлами и классами. (Или, я полагаю, вы можете назвать это перепиской 2 к 1).


перегружены тысячами строк кода?

наличие одного набора заголовочных / исходных файлов на класс в каталоге может показаться излишним. И если количество классов приближается к 100 или 1000, это может быть даже страшно.

но поиграв с источниками, следуя философии "давайте сложим все вместе", вывод состоит в том, что только тот, кто написал файл, имеет надежду не потеряться внутри. Даже с IDE легко пропустить вещи, потому что когда вы играете с источником 20 000 строк, вы просто закрываете свой ум для чего-то, что не совсем относится к вашей проблеме.

пример реальной жизни: иерархия классов, определенная в этих источниках тысяч строк, замкнулась в алмазное наследование, и некоторые методы были переопределены в дочерних классах методами с точно таким же кодом. Это было легко упустить из виду (кто хочет исследовать/проверить исходный код 20,000 строк?), и, когда исходный метод был изменен (ошибка поправка), эффект не был столь универсальным, как исключено.

зависимости становятся круговыми?

у меня была эта проблема с шаблонным кодом, но я видел аналогичные проблемы с обычным кодом C++ и C.

разбиение ваших источников на 1 заголовок на структуру / класс позволяет:

  • ускорить компиляцию, потому что вы можете использовать символ вперед объявление вместо включения целых объектов
  • циклические зависимости между классы ( § ) (т. е. класс A имеет указатель на B, А B имеет указатель на A)

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

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

для моей программы шаблона мне пришлось разделить мои заголовки на два файла:.HPP-файл, содержащий объявление/определение класса шаблона и.INL файл, содержащий определения методов класса.

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

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

(§) обратите внимание, что вы можете иметь циклические зависимости между классами, если вы знаете, какой класс кому принадлежит. Это обсуждение о классах, имеющих знания о существовании других классов, а не shared_ptr циклических зависимостей антиобразец.

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

одно дело, что должны соблюдается решение из нескольких заголовков и нескольких источников.

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

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

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

вопрос о циклических зависимостей

подчеркивание-d спрашивает:

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

underscore_d, 13 ноября в 23: 20

предположим, у вас есть 2 шаблона класса, A и B.

скажем, определение класса A (соотв. B) имеет указатель на B (соотв. Ля.) Давайте также рассмотрим методы класса A (соотв. Б) фактически методы вызова из B (соотв. Ля.)

у вас есть круговая зависимость как в определении классов, так и в реализациях их методов.

Если A и B были нормальными классами, а методы A и B были .CPP-файлы, не было бы никаких проблем: вы бы использовали прямое объявление, имели заголовок для каждого определения класса, тогда каждый CPP включал бы оба HPP.

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

это значит:

  1. заголовок определения А. определение.hpp и B. def.ГЭС
  2. заголовок реализации A. inl.hpp и B. inl.ГЭС
  3. для удобства" наивный " заголовок A. hpp и B. hpp

каждый заголовок будет иметь следующие черты:

  1. в A. def.ГЭС (ОТВ. B. def.hpp), у вас есть прямое объявление класса B (соотв. A), что позволит вам объявить указатель / ссылку на этот класс
  2. A. inl.ГЭС (ОТВ. B. inl.hpp) будет включать оба A. def.hpp и B. def.hpp, который позволит использовать методы из A (resp. B) использовать класс B (соотв. Ля.)
  3. А. ГЭС (респ. Б. ГЭС) будет напрямую включать как А. ПОБ.ГЭС и А. мнз.ГЭС (ОТВ. B. def.hpp и B. inl.hpp)
  4. конечно, все заголовки должны быть самодостаточными и защищенными защитниками заголовков

наивный пользователь будет включать A. hpp и / или B. hpp, таким образом, игнорируя все беспорядок.

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

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


мы делаем это на работе, просто проще найти материал, если класс и файлы имеют одно и то же имя. Что касается производительности, вы действительно не должны иметь 5000 классов в одном проекте. Если вы это сделаете, некоторые рефакторинга может быть в порядке.

тем не менее, есть случаи, когда у нас есть несколько классов в одном файле. И это когда это просто частный вспомогательный класс для основного класса файла.


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


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


большинство мест, где я работал, следовали этой практике. На самом деле я написал стандарты кодирования для BAE (Aust.) наряду с причинами, почему вместо того, чтобы просто вырезать что-то в камне без реального оправдания.

Что касается вашего вопроса об исходных файлах, это не так много времени для компиляции, но больше проблема в том, чтобы найти соответствующий фрагмент кода в первую очередь. Не все используют IDE. И зная, что вы просто ищете MyClass.h И MyClass.СРР действительно экономит время по сравнению с запуском " grep MyClass *.(h / cpp)" над кучей файлов, а затем отфильтровать #include MyClass.ч заявлениями...

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

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


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


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


Я нашел эти руководства особенно полезны, когда дело доходит до заголовочных файлов : http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Header_Files


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


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

Я лично группирую связанные классы в один файл, а затем даю такому файлу значимое имя, которое не придется меняться, даже если имя класса изменится. Наличие меньшего количества файлов также упрощает прокрутку дерева файлов. Я использую Visual Studio в Windows и Eclipse CDT в Linux, и у обоих есть сочетания клавиш, которые ведут вас прямо к объявлению класса, поэтому найти объявление класса легко и быстро.

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

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


то же самое правило применяется здесь, но он отмечает несколько исключений, где это разрешено так:

  • наследование деревья
  • классы, которые используются только в очень ограниченные возможности
  • некоторые утилиты просто помещаются в общий ' utils.h'