Чи є #pragma один раз безпечним включенням охоронця?


310

Я читав, що існує деяка оптимізація компілятора при використанні, #pragma onceщо може призвести до більш швидкої компіляції. Я усвідомлюю, що це нестандартно, і, таким чином, може виникнути проблема сумісності між платформами.

Це щось, що підтримується більшістю сучасних компіляторів на не-windows платформах (gcc)?

Я хочу уникати проблем зі збиранням платформи, але також хочу уникати зайвої роботи резервних охоронців:

#pragma once
#ifndef HEADER_H
#define HEADER_H

...

#endif // HEADER_H

Чи варто мене турбувати? Чи варто витрачати на це подальшу розумову енергію?


3
Задавши подібне запитання , я виявив, що, #pragma onceздається, уникнути деяких проблем із переглядом класу у VS 2008. Я з цього приводу позбувся включення охоронців та замінив їх усіма #pragma once.
SmacL

Відповіді:


188

Використання #pragma onceповинно працювати на будь-якому сучасному компіляторі, але я не бачу причин, щоб не використовувати стандартний #ifndefвключати вартості. Це працює просто чудово. Один нюанс в тому , що GCC не підтримує , #pragma onceперш ніж версія 3.4 .

Я також виявив, що, принаймні на GCC, він визнає, що стандарт #ifndefвключає функцію охорони та оптимізує його , тому він не повинен бути набагато повільніше, ніж #pragma once.


12
Це взагалі не повинно бути повільніше (з GCC все одно).
Джейсон Коко

54
Це не реалізовано таким чином. Натомість, якщо файл починається з #ifndef вперше і закінчується #endif, gcc запам'ятовує його і завжди пропускає, що включає в майбутньому, навіть не намагаючись відкрити файл.
Джейсон Коко

10
#pragma onceяк правило, швидше, оскільки файл не попередньо обробляється. ifndef/define/endifвсе одно вимагає попередньої обробки, тому що після цього блоку ви можете мати щось складене (теоретично)
Андрій


38
Для використання включають охоронці, є додаткова вимога, що ви повинні визначити новий символ, наприклад, як #ifndef FOO_BAR_Hправило, для такого файлу, як "foo_bar.h". Якщо ви пізніше перейменовуєте цей файл, чи слід відповідно відрегулювати захисні панелі включення відповідно до цієї конвенції? Крім того, якщо у вашому кодовому дереві є два різних foo_bar.h у двох різних місцях, ви повинні придумати два різних символи для кожного. Коротка відповідь полягає у використанні, #pragma onceі якщо вам справді потрібно компілювати в середовищі, яке не підтримує його, тоді продовжуйте додавати включені охоронці для цього середовища.
Брандін

327

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


36
Але ви також можете мати два файли з однаковою назвою в різних місцях, не турбуючись про створення різних #define NAMES, що є iften у вигляді HEADERFILENAME_H
Vargas

69
Ви також можете мати два або більше файлів із тим самим #define БЕЗКОШТОВНО, що не спричиняє кінця задоволення, тому я вважаю за краще один раз використовувати прагму.
Кріс Хуан-Лівер

107
Не переконливо ... Змініть систему складання на таку, яка не копіює файли навколо, а використовує натомість символьні посилання, або ж додайте один і той же файл лише з одного місця у кожному блоці перекладу. Звучить, як ваша інфраструктура - це безлад, який треба реорганізувати.
Яків Галка

3
А якщо у різних каталогах у вас різні файли з однаковим іменем, підхід #ifdef подумає, що вони є одним файлом. Отже, для одного є недолік, а для іншого - недолік.
rxantos

3
@rxantos, якщо файли відрізняються, #ifdefзначення макросу також може відрізнятися.
Мотті

63

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

Насправді, оскільки 99,98% часу #pragma onceповедінка є бажаною поведінкою, було б добре, якби запобігання багаторазового включення заголовка автоматично оброблялося компілятором, з #pragmaможливістю подвійного включення.

Але у нас є те, що ми маємо (крім того, що у вас, можливо, не було #pragma once).


48
Мені дуже хочеться стандартної #importдирективи.
Джон

10
Існує стандартна директива про імпорт: isocpp.org/blog/2012/11/… Але поки ще не тут. Я рішуче підтримую це.
AHelps

6
@AHelps Vaporware. Чи минуло майже п’ять років. Можливо, у 2023 році ти повернешся до цього коментаря і скажеш "Я тобі це сказав".
doug65536

Це не парне програмне забезпечення, але це лише на етапі Технічної специфікації. Модулі реалізовані у Visual Studio 2015 ( blogs.msdn.microsoft.com/vcblog/2015/12/03/… ) та в clang ( clang.llvm.org/docs/Modules.html ). І це імпорт, а не # імпорт.
AHelps

Слід перетворити його на C ++ 20.
Ionoclast Brigham

36

Я не знаю про якісь переваги від продуктивності, але це, безумовно, працює. Я використовую його у всіх моїх проектах C ++ (надано, я використовую компілятор MS). Я вважаю це більш ефективним, ніж використання

#ifndef HEADERNAME_H
#define HEADERNAME_H
...
#endif

Він виконує ту саму роботу і не заповнює препроцесор додатковими макросами.

GCC #pragma onceофіційно підтримує версію 3.4 .


25

GCC підтримує #pragma onceз 3.4, див. Http://en.wikipedia.org/wiki/Pragma_once для подальшої підтримки компілятора.

Я бачу, що я використовую #pragma onceна відміну від включення захисників, щоб уникнути помилок копіювання / вставки.

Давайте зіткнемося з цим: більшість з нас навряд чи запускає новий файл заголовка з нуля, а просто скопіює вже існуючий та модифікує його під наші потреби. Набагато простіше створити робочий шаблон, використовуючи #pragma onceзамість включення охоронців. Чим менше мені потрібно змінювати шаблон, тим менше я, швидше за все, натрапляю на помилки. Наявність однакових функцій захисту у різних файлах призводить до дивних помилок компілятора, і це потребує певного часу, щоб з'ясувати, що пішло не так.

TL; DR: #pragma onceпростіше у використанні.


11

Я використовую його і я задоволений цим, оскільки мені потрібно вводити набагато менше, щоб створити новий заголовок. Для мене це працювало чудово у трьох платформах: Windows, Mac та Linux.

У мене немає ніякої інформації про продуктивність, але я вважаю, що різниця між #pragma та include Guard не буде нічого порівняно з повільністю розбору граматики C ++. У цьому справжня проблема. Спробуйте скласти однакову кількість файлів і рядків, наприклад, компілятором C #, щоб побачити різницю.

Зрештою, використання охоронця чи прагми не матиме значення взагалі.


Мені не подобається #pragma один раз, але я вдячний, що ви вказуєте на відносні переваги ... Аналіз C ++ набагато дорожче, ніж усе інше, у "нормальному" робочому середовищі. Ніхто не компілює з віддаленої файлової системи, якщо час компіляції є проблемою.
Том

1
Re C ++ розбирає повільність проти C #. У C # вам не доведеться розбирати (буквально) тисячі LOC файлів заголовків (iostream, хтось?) Для кожного крихітного файлу C ++. Використовуйте попередньо складені заголовки, щоб зменшити цю проблему
Елі Бендерський,

11

Використання ' #pragma once' може не мати ніякого ефекту (воно не підтримується скрізь - хоча воно все більше підтримується), тому вам все одно потрібно використовувати умовний код компіляції, і в такому випадку, навіщо турбуватися з ' #pragma once'? Компілятор, ймовірно, оптимізує його у будь-якому випадку. Однак це залежить від вашої цільової платформи. Якщо всі ваші цілі підтримують його, то йдіть вперед і використовуйте його - але це повинно бути свідомим рішенням, тому що все пекло вийде з ладу, якщо ви лише використовуєте прагму, а потім перенесите на компілятор, який не підтримує її.


1
Я не погоджуюся з тим, що ви все одно повинні підтримувати охоронців. Якщо ви використовуєте #pragma один раз (або охоронці), це тому, що це викликає деякі конфлікти без них. Тож якщо він не підтримується вашим ланцюговим інструментом, проект просто не складеться, і ви перебуваєте точно в тій же ситуації, що і тоді, коли ви хочете скласти деякий ansi C на старому компіляторі K&R. Вам просто потрібно отримати більш сучасний ланцюжок або змінити код, щоб додати кілька охоронців. Усі пекельні зломки були б, якби програма збиралася, але не працювала.
kriss

5

Перевага від продуктивності полягає в тому, що не потрібно знову відкривати файл після того, як #pragma один раз буде прочитаний. За допомогою охоронців компілятору потрібно відкрити файл (що може затратити час), щоб отримати інформацію про те, що він не повинен знову включати його вміст.

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

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

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

#ifndef NR_TEST_H
#define NR_TEST_H
#pragma once

#include "Thing.h"

namespace MyApp
{
 // ...
}

#endif

Таким чином ви отримуєте найкраще з обох (кросплатформна та швидкість компіляції).

Оскільки вводити довше, я особисто використовую інструмент, який допомагає генерувати все це дуже хитро (Visual Assist X).


Чи Visual Studio не оптимізує захист #include таким, який є? Інші (краще?) Компілятори роблять так, і я думаю, це досить легко.
Том

1
Чому ви ставите pragmaпісля ifndef? Чи є користь?
користувач1095108

1
@ user1095108 Деякі компілятори використовуватимуть захисні заголовки як роздільник, щоб знати, чи містить файл лише код, який потрібно інстанціювати один раз. Якщо якийсь код знаходиться поза захисними заголовками, то весь файл може вважатися неминучим не один раз. Якщо той самий компілятор не підтримує прагму один раз, він ігнорує цю інструкцію. Тому розміщення прагми один раз всередині заголовків заголовка - це найзагальніший спосіб переконатися, що принаймні захисні заголовки можуть бути «оптимізовані».
Клаїм

4

Не завжди.

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52566 є прекрасним прикладом двох файлів, які повинні бути включені, але помилково вважається ідентичними через однакові часові позначки та вміст (не однакове ім'я файлу) .


10
Це буде помилка в компіляторі. (намагається взяти ярлик, який він не повинен приймати).
rxantos

4
#pragma onceнестандартне, тому все, що компілятор вирішить зробити, це "правильно". Звичайно, тоді ми можемо почати говорити про те, що "очікується" і що "корисно".
користувач7610

2

Використовуючи gcc 3.4 та 4.1 на дуже великих деревах (іноді використовуючи distcc ), я ще не бачу швидкості при використанні #pragma один раз, або в поєднанні зі стандартними включенням охоронців.

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

Я теж бажаю, щоб він був прийнятий на початку, але я можу побачити аргумент "Навіщо нам це потрібно, якщо ifndef працює прекрасно?". Враховуючи безліч темних куточків С та складності, включити охоронців є однією з найпростіших речей, що пояснюються самостійно. Якщо у вас є навіть невеликі знання про те, як працює препроцесор, вони повинні бути самостійними.

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


2

Сьогодні охоронець старої школи такий же швидкий, як колись #прагма. Навіть якщо компілятор не звертається до них спеціально, він все одно зупиниться, коли він побачить #ifndef ЩО БЕЗКОШТОВНО і ЩО БЕЗ визначено. Відкриття файлу сьогодні є дешевим брудом. Навіть якби було покращення, це було б у порядку мілісекунд.

Я просто не використовую #pragma один раз, оскільки це не має ніякої користі. Щоб уникнути зіткнення з іншими включеннями охоронців, я використовую щось на зразок: CI_APP_MODULE_FILE_H -> CI = Початкові дані компанії; APP = назва програми; решта - сама роз'яснення.


19
Чи не користь, що це набагато менше набирати текст?
Андрій

1
Зауважте, що кілька мілісекунд сто тисяч разів - це кілька хвилин. Великі проекти складаються з десяти тисяч файлів, включаючи десятки заголовків кожен. Враховуючи сучасні багатоядерні процесори, введення / виведення, зокрема відкриття багатьох невеликих файлів , є одним із головних вузьких місць.
Деймон

1
"Сьогодні до старої школи входять охоронці такі ж швидкі, як колись #прагма". Сьогодні, а також багато-багато років тому. Найдавніші документи на сайті GCC - це 2,95 з 2001 року, і оптимізація включення охоронців тоді не була новою. Це не остання оптимізація.
Jonathan Wakely

4
Основна перевага полягає в тому, що охоронці включають схильність до помилок та багатослівність. Надто просто мати два різних файли з однаковими іменами в різних каталогах (і охоронці включати можуть бути однаковим символом) або робити помилки копіювання-вставлення під час копіювання включати охоронці. Pragma колись менш схильний до помилок і працює на всіх основних платформах ПК. Якщо ви можете використовувати його, краще стиль.
AHelps

2

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

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

У компіляторах, які не підтримують прагму, я бачив ручні реалізації, які трохи громіздкі ..

#ifdef FOO_H
#include "foo.h"
#endif

Мені особисто подобається #pragma один раз підходити, оскільки це дозволяє уникнути клопоту іменувати зіткнення та потенційні помилки друку. Це також більш елегантний код у порівнянні. Однак, для портативного коду не повинно зашкодити мати обоє, якщо компілятор не скаржиться на це.


1
"Однак це означає, що сьогодні компілятори (включаючи GCC) є досить розумними, щоб ставитись до охоронців, як колись прагма". Вони робили це десятиліттями, може, довше, ніж #pragma onceіснували!
Джонатан Вейклі

Подумайте, ви мене зрозуміли неправильно. Я мав на увазі сказати перед прагмою один раз, всі компілятори матимуть декілька значень IO для одного і того ж файлу h, включеного кілька разів на етапі препроцесора. Сучасні реалізації закінчують використання кращого кешування файлів на етапі препроцесора. Незалежно від того, без прагм, етап препроцесора закінчується, включаючи все, що не входить до складу охоронців. З прагмою один раз весь файл залишився. З цієї точки зору, прагма все ще вигідна.
Шаммі

1
Ні, це неправильно, порядні компілятори залишають весь файл навіть без #pragma один раз, вони не відкривають файл вдруге і навіть не дивляться на нього вдруге, див. Gcc.gnu.org/onlinedocs/ cpp / Once-Only-Headers.html (це не має нічого спільного з кешуванням).
Jonathan Wakely

1
З вашого посилання, схоже, що оптимізація відбувається лише в cpp. Незалежно від цього, кешування дійсно грає. Як препроцесор знає, щоб включити код поза сторожами. Приклад ... extern int foo; #ifndef INC_GUARD #define INC_GUARD клас ClassInHeader {}; #endif У цьому випадку препроцесору доведеться включати зовнішній int foo; кілька разів, якщо ви включаєте один і той же файл кілька разів. Кінець дня, не надто сперечаючись з цим, поки ми зрозуміємо різницю між #pragma один раз і включимо охоронців та те, як різні компілятори поводяться з ними обома :)
Шаммі,

1
Очевидно, це не застосовує оптимізацію.
Jonathan Wakely

1

Якщо ми використовуємо msvc або Qt (до Qt 4.5), оскільки GCC (до 3.4), msvc обидві підтримують #pragma once, я не бачу причин не використовувати #pragma once.

Ім'я вихідного файлу, як правило, однакове ім’я класу, і ми знаємо, що колись нам потрібен рефактор , щоб перейменувати ім’я класу, тоді нам довелося також змінити #include XXXX, тому я думаю, що керівництво підтримувати #include xxxxxце не розумна робота. навіть із розширенням Visual Assist X, підтримка "xxxx" не є необхідною роботою.


1

Додаткова примітка людям, які думають, що завжди бажано автоматичне включення одноразових файлів заголовків: я створюю генератори коду, використовуючи подвійне або багаторазове включення файлів заголовків з десятиліть. Спеціально для генерації заглушок бібліотеки протоколів мені дуже зручно мати надзвичайно портативний та потужний генератор коду без додаткових інструментів та мов. Я не єдиний розробник, що використовує цю схему, як показують ці блоги X-Macros . Це неможливо зробити без відсутньої автоматичної охорони.


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

1
Наш багаторічний професійний досвід полягає в тому, що використання зрілої інфраструктури мови, програмного забезпечення та інструментів весь час дає нам як постачальникам послуг (вбудовані системи) величезну перевагу в продуктивності та гнучкості. Конкуренти, що розробляють програмне забезпечення та стеки на базі C ++, можуть замість того, щоб деякі їх розробники були більш щасливими на роботі. Але ми, як правило, багато разів перевершуємо їх на ринку, функціональності та гнучкості. Нижче недооцінюються переваги продуктивності від використання одного і того ж інструменту знову і знову. Натомість веб-розробники страждають від шляхів до багатьох фреймворків.
Марсель

Однак зауваження: не включає охоронців / # прагму один раз у кожному файлі заголовка проти самого принципу DRY. Я бачу вашу точку в X-Macroфункції, але це не головне використання включати, чи не повинно бути навпаки, як заголовок unguard / # pragma multi, якби ми дотримувалися DRY?
caiohamamura

DRY означає "Не повторюй себе". Це про людське. Те, що робить машина, не має нічого спільного з цією парадигмою. Шаблони C ++ багато разів повторюються, C-компілятори роблять це також (наприклад, розкручування циклу), і кожен комп'ютер повторює майже все неймовірне часто і швидко.
Марсель
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.