Каковы преимущества библиотеки только для заголовков и почему вы должны написать ее таким образом, чтобы не помещать реализацию в отдельный файл?
Каковы преимущества библиотеки только для заголовков и почему вы должны написать ее таким образом, чтобы не помещать реализацию в отдельный файл?
Ответы:
Бывают ситуации, когда библиотека только для заголовков является единственным вариантом, например, при работе с шаблонами.
Наличие библиотеки только для заголовков также означает, что вам не нужно беспокоиться о разных платформах, на которых может использоваться библиотека. Когда вы разделяете реализацию, вы обычно делаете это, чтобы скрыть детали реализации и распространять библиотеку как комбинацию заголовков и библиотек ( lib
, dll
или .so
файлов). Они, конечно, должны быть скомпилированы для всех различных операционных систем / версий, которые вы поддерживаете.
Вы также можете распространять файлы реализации, но это будет означать дополнительный шаг для пользователя - компиляцию вашей библиотеки перед ее использованием.
Конечно, это применимо в каждом конкретном случае . Например, библиотеки только для заголовков иногда увеличиваютразмер кода и время компиляции.
Преимущества библиотеки только для заголовков:
Недостатки библиотеки только для заголовков:
Большие объектные файлы. Каждый встроенный метод из библиотеки, который используется в каком-либо исходном файле, также получит слабый символ, внешнее определение в скомпилированном объектном файле для этого исходного файла. Это замедляет компилятор, а также замедляет компоновщик. Компилятор должен сгенерировать все это раздувание, а затем компоновщик должен отфильтровать его.
Более длинная компиляция. В дополнение к упомянутой выше проблеме раздувания компиляция займет больше времени, потому что заголовки по своей природе больше в библиотеке только для заголовков, чем в скомпилированной библиотеке. Эти большие заголовки нужно будет проанализировать для каждого исходного файла, который использует библиотеку. Другой фактор заключается в том, что эти файлы заголовков в библиотеке только для #include
заголовков должны содержать заголовки, необходимые для встроенных определений, а также заголовки, которые потребовались бы, если бы библиотека была построена как скомпилированная библиотека.
Более запутанная компиляция. Вы получаете намного больше зависимостей с библиотекой только для заголовков из-за тех дополнительных, которые #include
необходимы с библиотекой только для заголовков. Измените реализацию некоторых ключевых функций в библиотеке, и вам может потребоваться перекомпилировать весь проект. Внесите это изменение в исходный файл для скомпилированной библиотеки, и все, что вам нужно сделать, это перекомпилировать этот один исходный файл библиотеки, обновить скомпилированную библиотеку этим новым файлом .o и повторно связать приложение.
Человеку труднее читать. Даже имея лучшую документацию, пользователям библиотеки часто приходится прибегать к чтению заголовков библиотеки. Заголовки в библиотеке только для заголовков заполнены деталями реализации, которые мешают пониманию интерфейса. В скомпилированной библиотеке вы видите только интерфейс и краткий комментарий о том, что делает реализация, и обычно это все, что вам нужно. Это действительно все, что вам нужно. Вам не нужно знать детали реализации, чтобы знать, как использовать библиотеку.
detail
.
Я знаю, что это старый поток, но никто не упомянул интерфейсы 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).
Однако есть недостатки, упомянутые выше, такие как увеличенное время компиляции. Кроме того, у вас может быть бизнес, поэтому вы не можете передавать все детали реализации исходного кода всем своим пользователям на случай, если один из них украдет их.
Основное «преимущество» заключается в том, что для этого необходимо предоставить исходный код, поэтому вы получите отчеты об ошибках на машинах и компиляторах, о которых вы никогда не слышали. Когда библиотека полностью состоит из шаблонов, у вас нет особого выбора, но когда у вас есть выбор, использование только заголовка обычно является плохим инженерным выбором. (С другой стороны, конечно, заголовок означает только то, что вам не нужно документировать какую-либо процедуру интеграции.)
Встраивание может быть выполнено с помощью оптимизации времени соединения (LTO)
Я хотел бы выделить это, поскольку это снижает ценность одного из двух основных преимуществ библиотек только для заголовков: «вам нужно, чтобы определения в заголовке были встроены».
Минимальный конкретный пример этого показан по адресу: Оптимизация времени ссылки и встроенный
Таким образом, вы просто передаете флаг, и встраивание может выполняться в объектные файлы без какой-либо работы по рефакторингу, для этого больше нет необходимости хранить определения в заголовках.
Однако у LTO могут быть и свои недостатки: есть ли причина, почему не использовать оптимизацию времени соединения (LTO)?