Преимущества библиотек только для заголовков


101

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


В основном это шаблоны, но это также упростит распространение и использование.
BoBTFish 01

4
Я хотел бы добавить к
теме

Какие есть недостатки, о которых еще не было сказано?
NebulaFox 01

7
@moooeeeep: чтобы узнать о недостатках, вы можете прочитать параграф «Прекратить встраивание кода» на веб-странице C ++ Dos and Don'ts Chromium Projects.
Мистер C64 01

Ответы:


57

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

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

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

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


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

1
Я просто задал аналогичный вопрос о преимуществах производительности только заголовка. Как видите, разницы в размере кода нет. Однако реализация примера с использованием только заголовков работала на 7% медленнее. stackoverflow.com/questions/12290639/…
Homer6

@ Homer6, спасибо, что позвонили мне. Я никогда не измерял это.
Лучиан Григоре

1
@LuchianGrigore Я не мог найти никого, у кого было бы то же самое. Вот почему на ответ потребовалось время. Есть так много умозрительных комментариев «увеличивает размер кода» и «потребление памяти». Наконец-то у меня есть снимок различий, даже если это всего лишь один пример.
Homer6

@ Homer6. Почему бы не увеличить размер кода? Предполагая, что вы создаете несколько библиотек, которые используют только заголовок lib, а затем ваше приложение использует все эти библиотеки, вам нужно будет иметь несколько копий, а не связываться с одной общей библиотекой.
pooya13

61

Преимущества библиотеки только для заголовков:

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

Недостатки библиотеки только для заголовков:

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

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

  • Более запутанная компиляция. Вы получаете намного больше зависимостей с библиотекой только для заголовков из-за тех дополнительных, которые #includeнеобходимы с библиотекой только для заголовков. Измените реализацию некоторых ключевых функций в библиотеке, и вам может потребоваться перекомпилировать весь проект. Внесите это изменение в исходный файл для скомпилированной библиотеки, и все, что вам нужно сделать, это перекомпилировать этот один исходный файл библиотеки, обновить скомпилированную библиотеку этим новым файлом .o и повторно связать приложение.

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


22
Последний пункт на самом деле не имеет смысла. Любая разумная документация будет включать объявление функции, параметры, возвращаемые значения и т. Д. И все связанные комментарии. Если вам нужно обратиться к файлу заголовка, документация не удалась.
Thomas

6
@Thomas - Даже с лучшими из профессиональных библиотек мне часто приходится прибегать к чтению заголовка "отлично". Фактически, если так называемая «прекрасная» документация извлекается из кода плюс комментарии, мне обычно нравится читать заголовки. Код плюс комментарии говорят мне больше, чем автоматически созданная документация.
Дэвид Хаммен

2
Последний пункт недействителен. Заголовки уже заполнены деталями реализации в закрытых членах, поэтому это не похоже на то, что файл cpp скрывает все детали реализации. Вдобавок такие языки, как C #, по сути, по своей сути являются «только заголовками», а среда IDE заботится о том, чтобы скрыть детали («сворачивая» их вниз)
Марк Лаката,

2
@Tomas: Согласитесь, последний пункт - подделка. Вы можете легко разделить интерфейс и реализацию с помощью библиотек только для заголовков; у вас просто есть заголовок интерфейса #include детали реализации. Вот почему библиотеки Boost обычно включают подкаталог (и пространство имен) с именем detail.
Nemo

4
@ Томас: Я не согласен. Заголовочный файл - это обычно первое место, куда я обращаюсь за документацией. Если заголовок хорошо написан, во внешней документации часто нет необходимости.
Джоэл Корнетт

15

Я знаю, что это старый поток, но никто не упомянул интерфейсы ABI или конкретные проблемы компилятора. Так что я думал, что буду.

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

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

Например, поставщики компиляторов часто меняют свою реализацию STL между версиями. Если у вас есть функция в библиотеке, которая принимает std :: vector, тогда она ожидает, что байты в этом классе будут упорядочены так, как они были расположены при компиляции библиотеки. Если в новой версии компилятора поставщик улучшил эффективность std :: vector, тогда код пользователя видит новый класс, который может иметь другую структуру, и передает эту новую структуру в вашу библиотеку. Дальше все идет под откос ... Вот почему не рекомендуется передавать объекты STL через границы библиотеки. То же самое относится к типам C Run-Time (CRT).

Говоря об CRT, ваша библиотека и исходный код пользователя обычно должны быть связаны с одной и той же CRT. В Visual Studio, если вы создаете свою библиотеку с использованием многопоточной CRT, но пользователь ссылается на многопоточную отладочную CRT, у вас возникнут проблемы со связью, потому что ваша библиотека может не найти нужные символы. Я не могу вспомнить, какая это была функция, но для Visual Studio 2015 Microsoft сделала одну функцию CRT встроенной. Внезапно в заголовке оказалась не библиотека CRT, поэтому библиотеки, которые ожидали найти ее во время компоновки, больше не могли этого делать, и это приводило к ошибкам связи. В результате эти библиотеки потребовали перекомпиляции с Visual Studio 2015.

Вы также можете получить ошибки ссылок или странное поведение, если вы используете Windows API, но при сборке с другими настройками Unicode для пользователя библиотеки. Это связано с тем, что Windows API имеет функции, которые используют строки Unicode или ASCII и макросы / определяют, которые автоматически используют правильные типы на основе настроек Unicode проекта. Если вы передадите строку через границу библиотеки неправильного типа, во время выполнения все сломается. Или вы можете обнаружить, что программа вообще не ссылается.

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

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

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

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


8

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


0

Встраивание может быть выполнено с помощью оптимизации времени соединения (LTO)

Я хотел бы выделить это, поскольку это снижает ценность одного из двух основных преимуществ библиотек только для заголовков: «вам нужно, чтобы определения в заголовке были встроены».

Минимальный конкретный пример этого показан по адресу: Оптимизация времени ссылки и встроенный

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

Однако у LTO могут быть и свои недостатки: есть ли причина, почему не использовать оптимизацию времени соединения (LTO)?

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.