Является ли #pragma безопасным включением охраны?


311

Я читал, что существует некоторая оптимизация компилятора при использовании, #pragma onceкоторая может привести к более быстрой компиляции. Я признаю, что это нестандартно и, следовательно, может создать проблему кросс-платформенной совместимости.

Это то, что поддерживается большинством современных компиляторов на не-Windows платформах (gcc)?

Я хочу избежать проблем с компиляцией платформы, но также хочу избежать дополнительной работы защитников:

#pragma once
#ifndef HEADER_H
#define HEADER_H

...

#endif // HEADER_H

Должен ли я быть обеспокоен? Должен ли я потратить еще какую-нибудь умственную энергию на это?


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

Ответы:


189

Использование #pragma onceдолжно работать на любом современном компиляторе, но я не вижу причин, чтобы не использовать стандартный #ifndefinclude guard. Работает просто отлично. Единственное предостережение в том, что GCC не поддерживал #pragma onceдо версии 3.4 .

Я также обнаружил, что, по крайней мере в GCC, он распознает стандартную #ifndefзащиту include и оптимизирует его , поэтому он не должен быть намного медленнее, чем #pragma once.


12
Это не должно быть немного медленнее (с GCC в любом случае).
Джейсон Коко

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

10
#pragma onceобычно быстрее, потому что файл не подвергается предварительной обработке. ifndef/define/endifВ любом случае требуется предварительная обработка, потому что после этого блока вы можете получить что-то компилируемое (теоретически)
Андрей

13
Документы GCC по оптимизации макросов: gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html
Адриан,

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

329

#pragma once у него есть один недостаток (кроме нестандартного), и это если у вас один и тот же файл в разных местах (у нас это есть, потому что наша система сборки копирует файлы), тогда компилятор будет думать, что это разные файлы.


36
Но вы также можете иметь два файла с одним и тем же именем в разных местах, не беспокоясь о создании разных имен #define, например, в форме HEADERFILENAME_H
Варгас

69
Вы также можете иметь два или более файлов с одним и тем же #define WHATEVER, что не доставляет никакого удовольствия, поэтому я бы предпочел один раз использовать прагму.
Крис Хуан-Ливер

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

7
@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.
Ионокласт Бригам

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. Вам просто нужно получить более актуальную цепочку инструментов или изменить код, чтобы добавить некоторых охранников. Все, что сломалось бы, было бы, если бы программа компилировалась, но не работала.
Крис

5

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

Это теория только потому, что некоторые компиляторы автоматически не открывают файлы, в которых не было никакого кода для чтения, для каждой единицы компиляции.

В любом случае, это относится не ко всем компиляторам, поэтому в идеале нужно избегать использования #pragma, поскольку кросс-платформенный код вообще не является стандартным / не имеет стандартизированного определения и эффекта. Однако, на самом деле, это действительно лучше, чем охранники.

В конце концов, лучшее предложение, которое вы можете получить, чтобы быть уверенным в том, что ваш компилятор будет иметь лучшую скорость без необходимости проверять поведение каждого компилятора в этом случае, это использовать как pragma один раз, так и guard.

#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? Есть ли выгода?
user1095108

1
@ user1095108 Некоторые компиляторы будут использовать средства защиты заголовков в качестве разделителя, чтобы знать, содержит ли файл только код, который необходимо создать один раз. Если какой-то код находится за пределами защиты заголовков, тогда весь файл может рассматриваться как экземпляр более одного раза. Если тот же самый компилятор не поддерживает прагму один раз, он игнорирует эту инструкцию. Следовательно, размещение прагмы внутри защитников заголовков является наиболее общим способом убедиться, что по крайней мере защитники заголовков могут быть «оптимизированы».
Klaim

4

Не всегда.

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


10
Это было бы ошибкой в ​​компиляторе. (пытаясь использовать ярлык, он не должен брать).
rxantos

4
#pragma onceявляется нестандартным, поэтому все, что компилятор решит сделать, будет «правильным». Конечно, тогда мы можем начать говорить о том, что «ожидается», а что «полезно».
user7610

2

Использование gcc 3.4 и 4.1 на очень больших деревьях (иногда использование distcc ), мне еще предстоит увидеть какое-либо ускорение при использовании #pragma вместо или в сочетании со стандартными средствами защиты include.

Я действительно не понимаю, как это может сбить с толку старые версии gcc или даже других компиляторов, поскольку реальной экономии нет. Я не пробовал все разные де-линтеры, но готов поспорить, что это многих их запутает.

Я тоже хотел бы, чтобы он был принят на ранней стадии, но я вижу аргумент «Зачем нам это нужно, если ifndef работает отлично?». Учитывая много темных углов и сложностей С, включить охранников - это одна из самых простых и понятных вещей. Если у вас есть хотя бы небольшие знания о том, как работает препроцессор, они должны быть понятны.

Если вы заметили значительное ускорение, обновите свой вопрос.


2

В наши дни охранники старой школы так же быстры, как и #pragma. Даже если компилятор не обрабатывает их специально, он все равно остановится, когда увидит #ifndef WHATEVER и WHATEVER. Открытие файла сегодня очень дешево. Даже если бы было улучшение, это было бы порядка миллисекунд.

Я просто не использую #pragma один раз, так как это не приносит никакой пользы. Чтобы избежать столкновения с другими включенными охранниками, я использую что-то вроде: CI_APP_MODULE_FILE_H -> CI = Company Initials; APP = название приложения; остальное говорит само за себя.


19
Разве вы не выигрываете от того, что печатаете меньше?
Андрей

1
Обратите внимание, что несколько миллисекунд сто тысяч раз - это несколько минут. Большие проекты состоят из десяти тысяч файлов, включая десятки заголовков. Учитывая современные многоядерные процессоры, ввод / вывод, в частности открытие множества небольших файлов , является одним из главных узких мест.
Деймон

1
«В наши дни охранники старой школы так же быстры, как и прагма». Сегодня, а также много-много лет назад. Самые старые документы на сайте GCC предназначены для 2.95 с 2001 года, и оптимизация включения охранников тогда не была новой. Это не недавняя оптимизация.
Джонатан Уэйкли

4
Основное преимущество заключается в том, что охранники подвержены ошибкам и многословны. Слишком легко иметь два разных файла с одинаковыми именами в разных каталогах (и включаемые ограждения могут быть одним и тем же символом) или делать ошибки при копировании и вставке при копировании включающих ограждений. Однажды Pragma менее подвержена ошибкам и работает на всех основных платформах ПК. Если вы можете использовать его, это лучший стиль.
AHelps

2

Основным отличием является то, что компилятору пришлось открыть файл заголовка, чтобы прочитать include guard. Для сравнения, однажды прагма заставляет компилятор отслеживать файл и не выполнять какой-либо ввод-вывод файла, когда он сталкивается с другим включением для того же файла. Хотя это может показаться незначительным, оно может легко масштабироваться с огромными проектами, особенно без хорошего заголовка, включая дисциплины.

Тем не менее, в наши дни компиляторы (включая GCC) достаточно умны, чтобы когда-то относиться к таким элементам защиты, как прагма. то есть они не открывают файл и избегают штрафа ввода-вывода файла.

В компиляторах, которые не поддерживают прагму, я видел ручные реализации, которые немного громоздки ..

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

Лично мне нравится подход #pragma Once, так как он позволяет избежать проблем с именами коллизий и потенциальных ошибок опечаток. Это также более элегантный код для сравнения. Тем не менее, для переносимого кода не должно быть вреда иметь оба, если компилятор не жалуется на это.


1
«Тем не менее, в наши дни компиляторы (включая GCC) достаточно умны, чтобы когда-то относиться к таким охранникам, как прагма». Они делали это десятилетиями, может быть, дольше, чем #pragma onceсуществовало!
Джонатан Уэйкли

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

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

1
По вашей ссылке кажется, что оптимизация происходит только в cpp. Независимо от того, кэширование действительно входит в игру. Как препроцессор знает, что он должен включать код за пределами охраны? Пример ... extern int foo; #ifndef INC_GUARD #define INC_GUARD class ClassInHeader {}; #endif В этом случае препроцессор должен будет включать extern int foo; несколько раз, если вы включаете один и тот же файл несколько раз. В конце дня, нет особого смысла спорить по этому поводу, пока мы понимаем разницу между #pragma и включаем охранников и то, как различные компиляторы ведут себя с ними обоими :)
Shammi

1
Это не относится к оптимизации в этом, очевидно.
Джонатан Уэйкли

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 ++ на основе встраиваемых систем, могут найти некоторых своих разработчиков более довольными работой. Но мы обычно выигрываем у них время выхода на рынок, функциональность и гибкость многократно. Ничто не недооценивает прирост производительности от использования одного и того же инструмента снова и снова. Вместо этого веб-разработчики страдают от множества способов разработки.
Марсель

Однако обратите внимание: в каждый заголовочный файл не включается guards / # pragma один раз против самого принципа DRY. Я вижу вашу точку зрения в этой X-Macroфункции, но это не основное использование include, разве это не должно быть наоборот, как header unguard / # pragma multi, если мы будем придерживаться DRY?
Кайохамамура

СУХОЙ означает «Не повторяй себя». Это о человеке. То, что делает машина, не имеет ничего общего с этой парадигмой. Шаблоны C ++ многократно повторяются, C-компиляторы также делают это (например, развертывание цикла), и каждый Компьютер повторяет почти все невероятно часто и быстро.
Марсель
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.