Лично, мне вполне нравятся библиотеки только для заголовка, но существуют требования, они вызывают чрезмерное увеличение размера кода из-за сверхвстраивания (а также другая очевидная проблема более длительного времени компиляции).
Я задавался вопросом, сколько истина там к этим требованиям (то о чрезмерном увеличении размера)?
Кроме того, затраты 'выравниваются по ширине'? (Очевидно, существуют неизбежные случаи такой как тогда, когда это - библиотека, реализованная просто или главным образом с шаблонами, однако я больше интересуюсь случаем, где существует на самом деле доступный выбор.)
Я знаю, что нет никакого жесткого правила, инструкции, и т.д. насколько материал как это идет, но я просто пытаюсь получить ощущение того, какие другие думают по проблеме.
P.S. Да это - очень неопределенный и субъективный вопрос, я знаю, и таким образом, я отметил его как таковой.
Я работаю в компании, у которой есть собственный отдел "Middleware" для обслуживания нескольких сотен библиотек, которые обычно используются большим количеством команд.
Несмотря на то, что мы работаем в одной компании, мы стесняемся только заголовочного подхода и предпочитаем отдавать предпочтение бинарной совместимости перед производительностью из-за простоты обслуживания.
По общему мнению, выигрыш в производительности (если он есть) не будет стоить проблем.
Более того, так называемый "code-bloat" может оказать негативное влияние на производительность, так как большее количество кода, загружаемого в кэш, подразумевает большее количество промахов в кэше, и это убийцы производительности.
В идеальном мире я полагаю, что компилятор и компоновщик могут быть достаточно умны, чтобы НЕ генерировать эти правила "множественных определений", но пока это не так, я (лично) предпочту:
Почему бы вам не протестировать ? Подготовьте две библиотеки (один только заголовок, а другой без встраивания методов на пару строк) и проверить их соответствующую производительность в ВАШЕМ случае.
EDIT:
Это было указано на "jalf" (спасибо), что я должен уточнить, что я имел в виду именно бинарную совместимость.
2 версии данной библиотеки называются бинарной совместимостью, если вы можете (обычно) соединяться с той или иной без каких-либо изменений вашей собственной библиотеки.
Поскольку вы можете компоновать только с одной версией данной библиотеки Target
, все загруженные библиотеки, использующие Target
, будут эффективно использовать одну и ту же версию... и вот причина переходной активности этого свойства.
MyLib --> Lib1 (v1), Lib2 (v1)
Lib1 (v1) --> Target (v1)
Lib2 (v1) --> Target (v1)
Теперь, скажем, что нам нужно исправление в Target
для свойства, используемого только в Lib2
, мы поставляем новую версию (v2)
. Если (v2)
является бинарной совместимой с (v1)
, то мы можем сделать:
Lib1 (v1) --> Target (v2)
Lib2 (v1) --> Target (v2)
Однако если это не так, то мы сделаем это:
Lib1 (v2) --> Target (v2)
Lib2 (v2) --> Target (v2)
Да, вы правильно прочитали, несмотря на то, что Lib1
не требовало исправления, вы направляетесь на перестройку с новой версией Target
, потому что эта версия обязательна для обновленной Lib2
, а Executable
может ссылаться только на одну версию Target
.
С библиотекой только заголовков, так как у вас нет библиотеки, вы фактически не совместимы с двоичными файлами. Поэтому каждый раз, когда вы делаете какое-либо исправление (безопасность, критическая ошибка и т.д...), вы должны поставлять новую версию, и все библиотеки, которые зависят от вас (даже косвенно), должны будут быть перестроены против этой новой версии!
Over-inflining - это, вероятно, то, к чему следует обратиться вызывающему абоненту, настраивая его опции компилятора, а не вызывающему абоненту, пытающемуся управлять им с помощью очень грубых инструментов ключевого слова inline
и определений в заголовках. Например, GCC имеет -finline-limit
и друзей, поэтому вы можете использовать разные правила встраивания для разных единиц перевода. То, что для вас является перевыполнением, может не быть перевыполнением для меня, в зависимости от архитектуры, размера и скорости кэша инструкций, того, как используется функция и т.д. Не то, чтобы мне когда-либо приходилось делать такую настройку: на практике, когда об этом стоило беспокоиться, стоило переписывать, но это может быть совпадением. В любом случае, если я являюсь пользователем библиотеки, то при прочих равных условиях я бы предпочёл иметь возможность встраивать (с учётом моего компилятора, и который я, возможно, не возьму на себя), чем быть неспособным к встраиванию.
Я думаю, что ужас раздувания кода из заголовочных библиотек исходит больше от беспокойства о том, что компоновщик не сможет удалить лишние копии кода. Так что, независимо от того, будет ли функция на самом деле встроена на сайтах компоновщиков или нет, беспокойство заключается в том, что в конечном итоге вы получите вызываемую копию функции (или класса) для каждого объектного файла, который ее использует. Я не могу вспомнить, должны ли адреса, обращённые к встроенным функциям в разных единицах трансляции в Си++, сравниваться равными, но даже если предположить, что они равны, так что есть одна "каноническая" копия функции в связанном коде, это не обязательно означает, что компоновщик на самом деле удалит мертвые дубликаты функций. Если функция определена только в одной единице трансляции, вы можете быть достаточно уверены, что на каждую статическую библиотеку или исполняемый файл, использующий ее, будет только одна отдельная копия.
Честно говоря, я не знаю, насколько обоснован этот страх. Всё, над чем я работал, либо было настолько сильно ограничено в памяти, что мы использовали inline
только как функции static inline
настолько маленькие, что мы не ожидаем, что inlined-версия будет заметно больше, чем код для вызова, и не возражаем против дубликатов, либо настолько слабо ограничены, что нас не волнуют никакие дубликаты нигде. Я еще никогда не попадал на середину пути поиска и подсчета дубликатов на разных компиляторах. Иногда я слышал от других, что это проблема с кодом шаблонов, так что я верю, что в утверждениях есть правда.
Придумывая это сейчас, я думаю, что если вы поставляете библиотеку только для заголовков, пользователь всегда может испортить её, если она ему не нравится. Напишите новый заголовок, объявляющий все функции, и новый блок перевода, включающий определения. Функции, определённые в классах, должны будут быть перемещены во внешние определения, так что если вы хотите поддержать это использование без необходимости вилки вашего кода пользователем, вы можете избежать этого и предоставить два заголовка:
// declare.h
inline int myfunc(int);
class myclass {
inline int mymemberfunc(int);
};
// define.h
#include "declare.h"
int myfunc(int a) { return a; }
int myclass::mymemberfunc(int a) { return myfunc(a); }
Вызовы, которые беспокоятся о раздутом коде, вероятно, могут перехитрить свой компилятор, включив declare.h во все свои файлы, а затем написав:
// define.cpp
#include "define.h"
Скорее всего, им также нужно избежать целой программной оптимизации, чтобы быть уверенными, что код не будет вставлен в библиотеку, но тогда вы не можете быть уверены, что даже не-строчная функция не будет вставлена в библиотеку целой программной оптимизацией.
Абоненты, которые не беспокоятся о раздувании кода, могут использовать файл define.h во всех своих файлах.
По моему опыту, раздувание не было проблемой:
Библиотеки только для заголовков дают компиляторам большую возможность встраивания, но они не заставляют компиляторы встраиваться - многие компиляторы рассматривают ключевое слово inline как не более чем команду игнорировать несколько одинаковых определений.
У компиляторов обычно есть параметры для оптимизации, чтобы контролировать объем встраивания; / Os на компиляторах Microsoft.
Обычно лучше позволить компилятору решать вопросы, связанные со скоростью и размером. Вы увидите раздувание только тех вызовов, которые фактически были встроены, и компилятор будет встраивать их только в том случае, если его эвристика показывает, что встраивание улучшит производительность.
Я бы не стал рассматривать раздувание кода как причину, по которой следует держаться подальше от библиотек, содержащих только заголовки, но я бы посоветовал вам подумать о том, насколько подход, использующий только заголовки, увеличит время компиляции.
Согласен, встроенные библиотеки намного проще использовать.
Inline bloat в основном зависит от платформы разработки, с которой вы работаете - в частности, от возможностей компилятора / компоновщика. Я бы не ожидал, что это будет большой проблемой с VC9, за исключением нескольких угловых случаев.
Я видел некоторые заметные изменения в окончательном размере в некоторых местах большого проекта VC6, но трудно дать конкретный "приемлемый, если...". Вам нужно попробовать с вашим кодом в вашем devenv.
Второй проблемой может быть время компиляции, даже при использовании прекомпилированного заголовка (тоже есть компромиссы).
В-третьих, некоторые конструкции проблематичны - например, статические члены данных, разделяемые между единицами трансляции, или отказ от наличия отдельного экземпляра в каждой единице трансляции.
Я увидел следующий механизм, дающий пользователю возможность выбора:
// foo.h
#ifdef MYLIB_USE_INLINE_HEADER
#define MYLIB_INLINE inline
#else
#define MYLIB_INLINE
#endif
void Foo(); // a gazillion of declarations
#ifdef MYLIB_USE_INLINE_HEADER
#include "foo.cpp"
#endif
// foo.cpp
#include "foo.h"
MYLIB_INLINE void Foo() { ... }