Должен ли я использовать один заголовок для включения всех заголовков статической библиотеки? - PullRequest
4 голосов
/ 09 февраля 2010

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

Спасибо за любую помощь.

Ответы [ 5 ]

3 голосов
/ 11 февраля 2010

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

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

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

Есть и другое соображение. Если я собираюсь использовать ваш класс WhizzoString, мне не нужно устанавливать заголовки для SOAP, OpenGL и того, что у вас. На самом деле, я бы предпочел, чтобы WhizzoString.h включал только заголовки для типов и символов, которые являются частью общедоступного интерфейса (то есть того, что мне понадобится как пользователю вашего класса. ).

Насколько это возможно, вы должны попытаться переместить включения из WhizzoString.h в WhizzoString.cpp:

OK

// Only include the stuff needed for this class
#include "foo.h"  // Foo class
#include "bar.h"  // Bar class

public class WhizzoString
{
    private Foo   m_Foo;
    private Bar * m_pBar;
       .
       .
       .
}

ЛУЧШЕ:

// Only include the stuff needed by the users of this class
#include "foo.h"  // Foo class

class Bar; // Forward declaration

public class WhizzoString
{
    private Foo   m_Foo;
    private Bar * m_pBar;
       .
       .
       .
}

Если пользователям вашего класса никогда не приходится создавать или использовать тип Bar, и класс не содержит экземпляров Bar, тогда может быть достаточно предоставить только прямое объявление Bar в заголовочном файле (WhizzoString. у cpp будет #include "bar.h"). Это означает, что любой, включая WhizzoString.h, может избежать включения Bar.h и всего, что в него входит.

2 голосов
/ 09 февраля 2010

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

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

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

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

2 голосов
/ 09 февраля 2010

как насчет предоставления обоих вариантов:

#include <library.hpp> // include everything
#include <library/module.hpp> // only single module

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

1 голос
/ 20 июня 2018

Проблема с заголовками отдельных файлов подробно объяснена доктором Доббсом , экспертом по написанию компиляторов. НИКОГДА НЕ ИСПОЛЬЗУЙТЕ ОДИН ФАЙЛ ФАЙЛА !!! Каждый раз, когда заголовок включается в файл .cc / .cpp, его необходимо перекомпилировать, поскольку вы можете передать макросы файла для изменения скомпилированного заголовка. По этой причине один заголовочный файл значительно увеличит время компиляции без каких-либо преимуществ. С C ++ вы должны сначала оптимизировать для человеческого времени, а время компиляции - человеческое время. Вы никогда не должны, потому что это значительно увеличивает время компиляции, включает в себя больше, чем нужно для компиляции в любом заголовке, каждый модуль перевода (TU) должен иметь свой собственный файл реализации (.cc / .cpp), и каждый TU, названный с уникальными именами файлов; .

За десятилетие опыта разработки C ++ SDK я ВСЕГДА имею три файла в модуле EVERY . У меня есть config.h, который включается в почти каждого заголовочного файла, который содержит предварительные требования для всего модуля, такого как platform-config и stdint.h. У меня также есть файл global.h, который включает в себя все файлы заголовков в модуле; этот в основном предназначен для отладки ( подсказка перечислите ваши швы в файле global.h для лучшей проверки и упрощения отладки кода ). Ключевой отсутствующий фрагмент здесь заключается в том, что у вас действительно должен быть public.h файл, который включает ONLY ваш публичный API.

В библиотеках, которые плохо запрограммированы, таких как boost и их отвратительные имена классов lower_snake_case, они используют этот недоделанный наихудший метод использования detail (иногда называемого 'impl') шаблона проектирования папок, чтобы "скрыть" свои частные интерфейс. Существует длинная предыстория того, почему это наихудшая практика, но короткая история заключается в том, что она создает избыточную типизацию INSANE , которая превращает однострочные в многострочные, и это не соответствует UML, и это портит диаграмму зависимостей UML, что приводит к чрезмерно сложному коду и противоречивым шаблонам проектирования, например, дети на самом деле являются родителями и наоборот. Вы не хотите или не нуждаетесь в папке detail, вам нужно использовать заголовок public.h с набором родственных модулей БЕЗ ДОПОЛНИТЕЛЬНЫХ ИМЕННЫХ ИМЕН , где ваш detail является братом, а не ребенком, который находится в реатлии родителя. Предполагается, что пространства имен предназначены только для одной цели: для сопряжения вашего кода с кодом других людей, но если это ваш код, вы управляете им, и вам следует использовать уникальные имена классов и функций, потому что это плохая практика, когда вы используете не нужно, потому что это может привести к коллизии хеш-таблицы, что замедлит процесс компиляции. UML - лучшая практика, поэтому, если вы можете организовать заголовки так, чтобы они были совместимы с UML, тогда ваш код по определению является более надежным и переносимым. Файл public.h - это все, что вам нужно для предоставления только публичного API; спасибо.

1 голос
/ 09 февраля 2010

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

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

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

...