Урок №26. Header guards і директива #pragma once

  Юрій  | 

  Оновл. 4 Вер 2021  | 

 380

На цьому уроці ми розглянемо, що таке header guards і #pragma once в мові C++, а також навіщо вони потрібні і як їх правильно використовувати.

Проблема дублювання оголошень

Як ми вже знаємо з уроку про попередні оголошення, ідентифікатор може мати тільки одне оголошення. Таким чином, програма з двома оголошеннями однієї змінної отримає помилку компіляції:

Те ж саме стосується і функцій:

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

Розглянемо наступну програму:

math.h:

geometry.h:

main.cpp:

Ця, здавалося б, невинна програма, не скомпілюється! Проблема криється у визначенні функції у файлі math.h. Давайте детально розглянемо, що тут відбувається:

   Спочатку main.cpp підключає заголовок math.h, внаслідок чого визначення функції getSquareSides() копіюється в main.cpp.

   Після цього main.cpp підключає заголовковий файл geometry.h, який, в свою чергу, підключає math.h.

   В geometry.h знаходиться копія функції getSquareSides() (з файлу math.h), яка вже вдруге копіюється в main.cpp.

Таким чином, після виконання всіх директив #include, main.cpp матиме наступний вигляд:

Ми отримаємо дублювання визначень і помилку компіляції. Якщо ж розглядати кожен файл окремо, то помилок немає. Однак, в main.cpp, який підключає відразу два заголовки з одним і тим же визначенням функції, ми зіткнемося з проблемами. Якщо для geometry.h потрібна функція getSquareSides(), а для main.cpp потрібен як geometry.h, так і math.h, то яке ж рішення?

Header guards

Насправді рішення просте — використовувати header guards (захист підключення в мові C++). Header guards — це директиви умовної компіляції, які складаються з наступного:

Якщо підключити цей заголовок, то перше, що він зробить, — це перевірить, чи був раніше визначений ідентифікатор SOME_UNIQUE_NAME_HERE. Якщо ми вперше підключаємо цей заголовок, то SOME_UNIQUE_NAME_HERE ще не був визначений. Отже, ми визначаємо SOME_UNIQUE_NAME_HERE (за допомогою директиви #define) і виконується основна частина заголовку. Якщо ж ми раніше підключали цей заголовок, то SOME_UNIQUE_NAME_HERE вже був визначений. В такому випадку, при підключенні цього заголовку вдруге, його вміст буде проігноровано.

Всі ваші заголовки повинні мати header guards. SOME_UNIQUE_NAME_HERE може бути будь-яким ідентифікатором, але, як правило, в якості ідентифікатора використовується ім’я заголовка з закінченням _H. Наприклад, у файлі math.h ідентифікатором буде MATH_H:

math.h:

Навіть заголовкові файли зі Стандартної бібліотеки С++ використовують header guards. Якби ви поглянули на вміст заголовку iostream, то побачили наступне:

Але зараз давайте повернемося до нашого прикладу з math.h, де ми спробуємо виправити ситуацію за допомогою header guards:

math.h:

geometry.h:

main.cpp:

Тепер, при підключенні в main.cpp заголовку math.h, препроцесор побачить, що MATH_H ще не був визначений, і тому виконається директива визначення MATH_H і вміст math.h скопіюється в main.cpp. Потім main.cpp підключає заголовок geometry.h, який, в свою чергу, підключає math.h. Препроцесор бачить, що MATH_H вже раніше був визначений і вміст geometry.h не буде скопійовано в main.cpp.

Ось так можна боротися з дублюванням визначень за допомогою header guards.

#pragma once

Більшість компіляторів підтримують більш просту, альтернативну форму header guards, — директиву
#pragma:

Директива #pragma once використовується в якості header guards, але має додаткові переваги: вона коротша і менш схильна до помилок.

Однак, #pragma once не є офіційною частиною мови C++, і не всі компілятори її підтримують (хоча більшість сучасних компіляторів підтримують).

Я все ж рекомендую використовувати header guards, щоб зберегти максимальну сумісність вашого коду.

Тест

Додайте header guards до наступного заголовкового файлу:

add.h:

Відповідь

Оцінити статтю:

1 Зірка2 Зірки3 Зірки4 Зірки5 Зірок (10 оцінок, середня: 5,00 з 5)
Loading...

Залишити відповідь

Ваш E-mail не буде опублікований. Обов'язкові поля відмічені *