Разбиты тысячи строк кода?
Наличие одного набора заголовочных / исходных файлов на класс в каталоге может показаться излишним. И если число классов приближается к 100 или 1000, это может быть даже пугающим.
Но, поиграв с источниками, следуя философии «давайте соединим все», мы пришли к выводу, что только тот, кто написал файл, надеется, что его не потеряют внутри. Даже с IDE легко пропустить вещи, потому что когда вы играете с источником в 20000 строк, вы просто закрываете свой разум для всего, что не относится к вашей проблеме.
Пример из реальной жизни: иерархия классов, определенная в этих тысячах исходных строк, замкнулась на алмазное наследование, а некоторые методы были переопределены в дочерних классах методами с точно таким же кодом. Это было легко упущено (кто хочет исследовать / проверить исходный код на 20 000 строк?), И когда оригинальный метод был изменен (исправление ошибок), эффект был не таким универсальным, как исключение.
Зависимости становятся круглыми?
У меня была эта проблема с шаблонным кодом, но я видел похожие проблемы с обычным кодом C ++ и C.
Разделение ваших источников на 1 заголовок для структуры / класса позволяет:
- Ускорение компиляции, поскольку вы можете использовать прямое объявление символа вместо включения целых объектов
- Имеют циклические зависимости между классами (§) (то есть класс A имеет указатель на B, а B имеет указатель на A)
В исходном коде зависимости классов могут приводить к регулярному перемещению классов вверх и вниз по файлу, просто чтобы компилировать заголовок. Вы не хотите изучать эволюцию таких движений при сравнении одного и того же файла в разных версиях.
Наличие отдельных заголовков делает код более модульным, быстрее компилируется и облегчает изучение его эволюции с помощью различных версий diffs
Для моей шаблонной программы мне пришлось разделить мои заголовки на два файла: файл .HPP, содержащий объявление / определение класса шаблона, и файл .INL, содержащий определения указанных методов класса.
Помещение всего этого кода в один и только один уникальный заголовок будет означать помещение определений классов в начале этого файла и определений методов в конце.
И затем, если кому-то понадобится только небольшая часть кода с решением с одним заголовком, ему все равно придется платить за более медленную компиляцию.
(§) Обратите внимание, что вы можете иметь циклические зависимости между классами, если знаете, какой класс кому принадлежит. Это обсуждение классов, которые знают о существовании других классов, а не круговых зависимостей shared_ptr antipattern.
Последнее слово: заголовки должны быть самодостаточными
Одна вещь, однако, должна соблюдаться решением с несколькими заголовками и несколькими источниками.
Когда вы включаете один заголовок, независимо от того, какой заголовок, ваш источник должен компилироваться без ошибок.
Каждый заголовок должен быть самодостаточным. Вы должны разрабатывать код, а не охотиться за сокровищами, запустив проект с более чем 10000 исходных файлов, чтобы найти, какой заголовок определяет символ в заголовке 1000 строк, который нужно включить только из-за one перечисления. *
Это означает, что либо каждый заголовок определяет, либо заранее объявляет все используемые им символы, либо включает все необходимые заголовки (и только необходимые заголовки).
Вопрос о круговых зависимостях
underscore-d спрашивает:
Можете ли вы объяснить, как использование отдельных заголовков влияет на циклические зависимости? Я не думаю, что это так. Мы можем тривиально создать циклическую зависимость, даже если оба класса полностью объявлены в одном и том же заголовке, просто путем предварительного объявления одного перед тем, как мы объявим дескриптор для него в другом. Все остальное кажется отличным моментом, но идея о том, что отдельные заголовки облегчают циклические зависимости, кажется далекой
underscore_d, 13 ноября в 23:20
Допустим, у вас есть 2 шаблона классов, A и B.
Допустим, определение класса A (соответственно B) имеет указатель на B (соответственно A). Скажем также, что методы класса A (соответственно B) фактически вызывают методы из B (соответственно A).
У вас круговая зависимость как в определении классов, так и в реализации их методов.
Если бы A и B были обычными классами, а методы A и B были в файлах .CPP, проблем не было бы: вы использовали бы предварительное объявление, имели бы заголовок для каждого определения класса, тогда каждый CPP включал бы оба HPP .
Но поскольку у вас есть шаблоны, вам действительно нужно воспроизвести эти шаблоны выше, но только с заголовками.
Это означает:
- заголовок определения A.def.hpp и B.def.hpp
- заголовок реализации A.inl.hpp и B.inl.hpp
- для удобства "наивный" заголовок A.hpp и B.hpp
Каждый заголовок будет иметь следующие черты:
- В A.def.hpp (соответственно B.def.hpp) у вас есть предварительное объявление класса B (соответственно A), которое позволит вам объявить указатель / ссылку на этот класс
- A.inl.hpp (соответственно B.inl.hpp) будет включать в себя как A.def.hpp, так и B.def.hpp, что позволит методам из A (соответственно B) использовать класс B (соответственно . А).
- A.hpp (соответственно B.hpp) будет напрямую включать в себя как A.def.hpp, так и A.inl.hpp (соответственно B.def.hpp и B.inl.hpp)
- Конечно, все заголовки должны быть самодостаточными и защищены защитниками заголовков
Наивный пользователь будет включать A.hpp и / или B.hpp, таким образом игнорируя весь беспорядок.
И эта организация означает, что разработчик библиотеки может решать круговые зависимости между A и B, сохраняя оба класса в отдельных файлах, легко ориентируясь, как только вы поймете схему.
Обратите внимание, что это был крайний случай (два шаблона знают друг друга). Я ожидаю, что большинству кода не нужен этот трюк.