Які переваги має лише бібліотека заголовків, і чому ви пишете її таким чином, щоб не покласти реалізацію в окремий файл?
Які переваги має лише бібліотека заголовків, і чому ви пишете її таким чином, щоб не покласти реалізацію в окремий файл?
Відповіді:
Бувають ситуації, коли єдиним варіантом є лише бібліотека заголовків, наприклад, коли йдеться про шаблони.
Наявність бібліотеки лише для заголовків також означає, що вам не доведеться турбуватися про різні платформи, де бібліотека може використовуватися. Коли ви відокремлюєте реалізацію, ви зазвичай робите це, щоб приховати деталі реалізації та розподілити бібліотеку як комбінацію заголовків та бібліотек ( lib
, dll
або.so
файлів файлів). Вони, звичайно, повинні бути скомпільовані для всіх різних операційних систем / версій, які ви пропонуєте.
Ви також можете розповсюджувати файли реалізації, але це означатиме додатковий крок для користувача - складання вашої бібліотеки перед її використанням.
Звичайно, це стосується кожного конкретного випадку . Наприклад, іноді збільшуються бібліотеки лише із заголовкамирозмір коду & час складання.
Переваги бібліотеки лише для заголовків:
Недоліки бібліотеки лише для заголовків:
Більші файли об’єктів. Кожен вбудований метод із бібліотеки, який використовується в якомусь вихідному файлі, також отримає слабкий символ, поза рядком визначення у складеному об'єктному файлі для цього вихідного файлу. Це уповільнює компілятор, а також уповільнює компоновщик. Компілятор повинен генерувати все, що роздувається, а потім компонувальник повинен це відфільтрувати.
Більш довга компіляція. На додаток до проблеми здуття, згаданої вище, компіляція триватиме довше, оскільки заголовки за своєю суттю більші, ніж у бібліотеки лише заголовків, ніж у скомпільованої бібліотеки. Ці великі заголовки потрібно буде проаналізувати для кожного вихідного файлу, який використовує бібліотеку. Інший фактор полягає в тому, що ці файли заголовків у бібліотеці, що містить лише #include
заголовки, повинні мати заголовки, необхідні для вбудованих визначень, а також заголовки, які були б потрібні, якби бібліотека була побудована як скомпільована бібліотека.
Більш заплутана компіляція. Ви отримуєте набагато більше залежностей з бібліотекою лише заголовків, оскільки додаткові #include
s потрібні для бібліотеки лише заголовків. Змініть реалізацію якоїсь ключової функції в бібліотеці, і вам може знадобитися перекомпілювати весь проект. Внесіть зміни у вихідний файл для скомпільованої бібліотеки, і все, що вам потрібно зробити, - це перекомпілювати цей вихідний файл бібліотеки, оновити скомпільовану бібліотеку цим новим файлом .o та знову зв’язати програму.
Людині важче читати. Навіть маючи найкращу документацію, користувачам бібліотеки часто доводиться вдаватися до читання заголовків бібліотеки. Заголовки в бібліотеці, що містить лише заголовки, заповнені деталями реалізації, які заважають зрозуміти інтерфейс. Завдяки скомпільованій бібліотеці все, що ви бачите, - це інтерфейс та короткий коментар щодо того, що робить реалізація, і це, як правило, все, що вам потрібно. Це насправді все, що ви повинні хотіти. Вам не потрібно знати деталі реалізації, щоб знати, як користуватися бібліотекою.
detail
.
Я знаю, що це стара тема, але ніхто не згадував про інтерфейси ABI чи конкретні проблеми компілятора. Тож я думав, що буду.
В основі цього лежить концепція того, що ви або пишете бібліотеку із заголовком для розповсюдження людям, або повторно використовуєте себе проти того, щоб мати все в заголовку. Якщо ви думаєте повторно використовувати заголовок та вихідні файли та перекомпілювати їх у кожному проекті, це насправді не стосується.
В основному, якщо ви компілюєте свій код C ++ і створюєте бібліотеку з одним компілятором, тоді користувач намагається використовувати цю бібліотеку з іншим компілятором або іншою версією того самого компілятора, тоді ви можете отримати помилки компонувальника або дивну поведінку під час виконання через бінарну несумісність.
Наприклад, постачальники компіляторів часто змінюють реалізацію STL між версіями. Якщо у вас є функція в бібліотеці, яка приймає std :: vector, то вона очікує, що байти в цьому класі будуть розташовані так, як вони були розташовані під час компіляції бібліотеки. Якщо в новій версії компілятора постачальник покращив ефективність std :: vector, тоді код користувача бачить новий клас, який може мати іншу структуру, і передає цю нову структуру у вашу бібліотеку. Звідти все йде вниз ... Ось чому рекомендується не передавати об’єкти STL через межі бібліотеки. Те саме стосується типів часу виконання C (CRT).
Розмовляючи про ЕПТ, ваша бібліотека та вихідний код користувача, як правило, повинні бути пов'язані з однією і тією ж ЕПТ. З Visual Studio, якщо ви створюєте свою бібліотеку за допомогою багатопотокової ЕПТ, але користувач посилається на багатопотокову налагоджувальну ЕПТ, у вас виникнуть проблеми із посиланнями, оскільки ваша бібліотека може не знайти потрібні символи. Я не пам’ятаю, яка це була функція, але для Visual Studio 2015 Microsoft зробила одну функцію ЕПТ вбудованою. Раптом це було в заголовку, а не в бібліотеці ЕПТ, тому бібліотеки, які очікували знайти його під час зв’язку, більше не могли робити, і це спричинило помилки при зв’язку. Результатом стало те, що ці бібліотеки потребували перекомпіляції з Visual Studio 2015.
Ви також можете отримати помилки посилання або дивну поведінку, якщо ви використовуєте Windows API, але будуєте з різними налаштуваннями Unicode для користувача бібліотеки. Це пояснюється тим, що API Windows має функції, які використовують або рядки Unicode, або ASCII, і макроси / визначає, які автоматично використовують правильні типи на основі параметрів Unicode проекту. Якщо ви передаете рядок через межі бібліотеки неправильного типу, то речі порушуються під час виконання. Або ви можете виявити, що програма не пов’язує спочатку.
Це також стосується передачі об’єктів / типів через межі бібліотек від інших сторонніх бібліотек (наприклад, власний вектор або матриця GSL). Якщо стороння бібліотека змінить заголовок між компіляцією бібліотеки та користувачем, який компілює код, тоді все буде зламано.
В основному для безпеки єдині речі, які ви можете передати через межі бібліотеки, вбудовані в типи та звичайні старі дані (POD). В ідеалі будь-який POD повинен мати структури, визначені у ваших власних заголовках, і не покладатися на жодні сторонні заголовки.
Якщо ви надаєте бібліотеку лише для заголовків, тоді весь код компілюється з однаковими налаштуваннями компілятора та проти тих самих заголовків, тому багато цих проблем зникає (надання версії третіх частково бібліотек, які ви та ваш користувач використовуєте, сумісні з API).
Однак є негативи, які були згадані вище, такі як збільшений час складання. Крім того, ви можете керувати бізнесом, тож ви можете не захотіти передавати всі деталі реалізації вихідного коду всім своїм користувачам на випадок, якщо хтось із них викраде його.
Головна "перевага" полягає в тому, що для цього потрібно надати вихідний код, тож ви отримаєте звіти про помилки на машинах та компілятори, про які ви ніколи не чули. Коли бібліотека - це цілком шаблони, у вас немає великого вибору, але коли у вас є вибір, лише заголовок, як правило, є поганим технічним вибором. (З іншого боку, звичайно, заголовок означає лише те, що вам не потрібно документувати жодну процедуру інтеграції.)
Вбудовування можна здійснити за допомогою оптимізації часу зв'язку (LTO)
Я хотів би це підкреслити, оскільки це зменшує значення однієї з двох основних переваг бібліотек лише для заголовків: "вам потрібні визначення в заголовку для вбудовування".
Мінімальний конкретний приклад цього наведено у: Оптимізація часу зв'язку та вбудована
Отже, ви просто передаєте прапор, і вбудовування можна виконувати в об’єктних файлах без будь-якої роботи з рефакторингу, і для цього більше не потрібно зберігати визначення в заголовках.
Однак LTO може мати і свої мінуси: чи є причина, чому не використовувати оптимізацію часу зв'язку (LTO)?