Забезпечення чіткого включення заголовків у файл CPP


10

Я думаю, що це загальнодобрана практика #includeзаголовка для будь-яких типів, що використовуються у файлі CPP, незалежно від того, що вже включено через файл HPP. Так, наприклад, я можу #include <string>як у своїй ГЕС, так і в CPP, хоча я все-таки можу компілювати, якби пропустив її в CPP. Таким чином, мені не потрібно турбуватися про те, використовувала моя ГЕС попередня декларація чи ні.

Чи є інструменти, які можуть застосувати цей #includeстиль кодування? Чи слід застосовувати цей стиль кодування?

Оскільки препроцесору / компілятору не важливо #include, походить це від HPP чи CPP, я не отримую зворотнього зв'язку, якщо забуду дотримуватися цього стилю.

Відповіді:


7

Це один із тих типів стандартів кодування "повинен", а не "повинен". Причина полягає в тому, що вам досить доведеться написати аналізатор C ++ для його виконання.

Дуже поширеним правилом для файлів заголовків є те, що вони повинні стояти самостійно. Файл заголовка не повинен вимагати, щоб деякі інші заголовні файли були #included, перш ніж включати заголовки. Це вимога, яку можна перевірити. З огляду на кілька випадкових заголовків foo.hh, слід скомпілювати та запустити наступне:

#include "foo.hh"
int main () {
   return 0;
}

Це правило має наслідки щодо використання інших класів у деяких заголовках. Іноді цих наслідків можна уникнути, якщо вперед оголосити ці інші класи. Це неможливо з великою кількістю стандартних бібліотечних класів. Немає способу переслати оголошення про шаблони, такі як std::stringабо std::vector<SomeType>. Ви повинні мати #includeці заголовки STL у заголовку, навіть якщо єдиним використанням цього типу є аргумент функції.

Ще одна проблема полягає в речах, які ви випадково перетягуєте. Приклад: врахуйте наступне:

файл foo.cc:

#include "foo.hh"
#include "bar.hh"

void Foo::Foo () : bar() { /* body elided */ }

void Foo::do_something (int item) {
   ...
   bar.add_item (item);
   ...
}

Тут barпредставлений Fooтип даних класу Bar. Ви зробили правильно тут і включили #included bar.hh, хоча це повинно було бути включено до заголовка, що визначає клас Foo. Однак ви не включили речі, якими користуються Bar::Bar()та Bar::add_item(int). Є багато випадків, коли ці дзвінки можуть призвести до додаткових зовнішніх посилань.

Якщо ви проаналізуєте foo.oза допомогою такого інструменту nm, виявиться, що функції в foo.ccвикликають всі види матеріалів, для яких ви не зробили відповідного #include. Тож чи варто додати #includeдирективи для цих випадкових зовнішніх посилань foo.cc? Відповідь абсолютно ні. Проблема полягає в тому, що дуже важко відрізнити ті функції, які викликаються випадково, від тих, які викликаються безпосередньо.


2

Чи слід застосовувати цей стиль кодування?

Напевно, ні. Моє правило таке: включення заголовкового файлу не може залежати від порядку.

Ви можете легко перевірити це за допомогою простого правила, що перший файл, що входить у xc, - це xh


1
Чи можете ви детальніше зупинитися на цьому? Я не бачу, як це насправді підтвердить незалежність замовлення.
detly

1
він насправді не гарантує незалежність замовлення - він просто гарантує, що #include "x.h"буде працювати, не вимагаючи деяких попередніх #include. Це досить добре, якщо ви не зловживаєте #define.
Кевін Клайн

1
О Я бачу. Ще гарна ідея тоді.
detly

2

Якщо вам потрібно застосувати правило, певні файли заголовків повинні стояти самостійно, ви можете скористатися інструментами, які у вас є. Створіть базовий makefile, який збирає кожен заголовок окремо, але не створює об’єктний файл. Ви зможете вказати, у якому режимі компілювати файл заголовка в режимі (C або C ++) і переконатися, що він може стояти самостійно. Ви можете зробити обґрунтоване припущення, що вихід не містить помилкових позитивних результатів, усі необхідні залежності оголошуються і результат є точним.

Якщо ви використовуєте IDE, ви все одно зможете це зробити без makefile (залежно від вашого IDE). Просто створіть додатковий проект, додайте файли заголовків, які ви хочете перевірити, та змініть налаштування, щоб компілювати його у вигляді файлів C або C ++. Наприклад, у MSVC ви б змінили налаштування "Тип елемента" у "Властивості конфігурації-> Загальне".


1

Я не думаю, що такий інструмент існує, але я був би радий, якщо якась інша відповідь спростує мене.

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

Єдиний спосіб такого інструменту міг би працювати, якби він міг скинути свою таблицю символів лише до вмісту файлу заголовка, який він обробив, але тоді ви зіткнетеся з проблемою, що заголовки, що формують зовнішній API бібліотеки, делегують фактичні декларації внутрішні заголовки.
Наприклад, <string>в GCC libc ++ реалізація нічого не декларує, але вона просто включає купу внутрішніх заголовків, які містять фактичні декларації. Якщо інструмент скидає свою таблицю символів просто на те, що було оголошено <string>само собою, це було б нічого.
Ви могли б диференціюватися інструмент між #include ""і #include <>, але це не допоможе вам , якщо зовнішня бібліотека використовує , #include ""щоб включити внутрішні заголовки в API.


1

цього не #Pragma onceдосягає? Ви можете включити що-небудь стільки разів, скільки хочете, безпосередньо або через ланцюжок включає, і доки #Pragma onceпоруч із кожним із них є заголовок, додається лише один раз.

Щодо її виконання, можливо, ви могли б створити систему збирання, яка просто включає кожен заголовок сам із собою якусь основну функцію, що забезпечує його компіляцію. #ifdefланцюг включає найкращі результати при використанні методу тестування.


1

Завжди включайте файли заголовків у файл CPP. Це не тільки значно скоротить час компіляції, але й заощадить вам багато проблем, якщо ви вирішите скористатися заздалегідь складеними заголовками. З мого досвіду, навіть робити проблеми з поданням декларацій варто лише на практиці. Правило перерви лише в разі необхідності.


Чи можете ви пояснити, як це "значно скоротить час компіляції"?
Мауг каже, що поверніть Моніку

1
Це гарантує, що кожен компіляційний блок (CPP-файл) лише тягне за собою мінімум включених файлів під час компіляції. В іншому випадку, якщо ви вводите файли H, то помилкові ланцюги залежностей формуються швидко, і ви в кінцевому підсумку витягуєте всі включені з кожною компіляцією.
Gvozden

Що стосується включення охоронців, я можу поставити під сумнів "значною мірою", але я вважаю, що доступ до диска (назустріч виявленню цих індикаторних датчиків) "повільний", тому це відступить точку. Дякую за уточнення
Мауг каже, що поверніть Моніку

0

Я б сказав, що у цієї конвенції є і переваги, і недоліки. З одного боку, приємно точно знати, що включає ваш файл .cpp. З іншого боку, список включених може легко вирости до смішного розміру.

Один із способів заохотити цю конвенцію - не включати нічого у власні заголовки, а лише у файли .cpp. Тоді будь-який .cpp-файл, що використовує ваш заголовок, не збиратиметься, якщо ви явно не включите всі інші заголовки, від яких залежить.

Ймовірно, тут є якийсь розумний компроміс. Наприклад, ви можете вирішити, що нормально включати стандартні заголовки бібліотек у власні заголовки, але не більше.


3
Якщо у вас відпустка включає заголовки, то включення порядку починає мати значення ... і я точно хочу цього уникнути.
М. Дадлі

1
-1: Створює кошмар залежності. Удосконалення до xh може зажадати змін у ВСІМ .cpp-файлі, що включає xh
kevin cline

1
@ M.Dudley, Як я вже сказав, є переваги і недоліки.
Діма
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.