Является ли #pragma безопасным включением охраны?
Я читал, что есть некоторая оптимизация компилятора при использовании #pragma once
что может привести к более быстрой компиляции. Я признаю, что это нестандартно и, следовательно, может создать проблему кросс-платформенной совместимости.
Это то, что поддерживается большинством современных компиляторов на не-Windows платформах (gcc)?
Я хочу избежать проблем с компиляцией платформы, но также хочу избежать дополнительной работы защитников:
#pragma once
#ifndef HEADER_H
#define HEADER_H
...
#endif // HEADER_H
Должен ли я быть обеспокоен? Должен ли я потратить еще какую-нибудь умственную энергию на это?
15 ответов
С помощью #pragma once
должен работать на любом современном компиляторе, но я не вижу причин, чтобы не использовать стандарт #ifndef
включить охрану. Работает просто отлично. Единственное предостережение в том, что GCC не поддерживает #pragma once
до версии 3.4.
Я также обнаружил, что, по крайней мере, на GCC, он признает стандарт #ifndef
включить охрану и оптимизировать его, поэтому он не должен быть намного медленнее, чем #pragma once
,
#pragma once
у него есть один недостаток (кроме нестандартного), и это если у вас один и тот же файл в разных местах (у нас это есть, потому что наша система сборки копирует файлы), тогда компилятор будет думать, что это разные файлы.
Хотелось бы #pragma once
(или что-то подобное) было в стандарте. Включить охранников не так уж и сложно (но их, кажется, немного трудно объяснить людям, изучающим язык), но это кажется незначительным раздражением, которого можно было бы избежать.
На самом деле, так как 99,98% времени, #pragma once
поведение - желаемое поведение, было бы неплохо, если бы предотвращение многократного включения заголовка было автоматически обработано компилятором с #pragma
или что-то, чтобы позволить двойной в том числе.
Но у нас есть то, что у нас есть (кроме того, что у вас может не быть #pragma once
).
Я не знаю о каких-либо преимуществах производительности, но это, безусловно, работает. Я использую его во всех своих проектах C++ (если я использую компилятор MS). Я считаю, что это более эффективно, чем использование
#ifndef HEADERNAME_H
#define HEADERNAME_H
...
#endif
Он выполняет ту же работу и не заполняет препроцессор дополнительными макросами.
GCC поддерживает #pragma once
официально с версии 3.4.
GCC поддерживает #pragma once
начиная с версии 3.4, см. http://en.wikipedia.org/wiki/Pragma_once для дальнейшей поддержки компилятора.
Большой плюс, который я вижу при использовании #pragma once
в отличие от включения защиты, чтобы избежать ошибок копирования / вставки.
Посмотрим правде в глаза: большинство из нас едва ли начинают новый заголовочный файл с нуля, а просто копируют существующий и модифицируют его в соответствии с нашими потребностями. Гораздо проще создать рабочий шаблон, используя #pragma once
вместо того, чтобы включить охранников. Чем меньше мне нужно изменить шаблон, тем меньше вероятность возникновения ошибок. Наличие одного и того же встроенного средства защиты в разных файлах приводит к странным ошибкам компилятора, и требуется некоторое время, чтобы выяснить, что пошло не так.
TL; DR: #pragma once
проще в использовании.
Я использую его, и я доволен им, поскольку мне приходится печатать намного меньше, чтобы создать новый заголовок. У меня это работало нормально на трех платформах: Windows, Mac и Linux.
У меня нет никакой информации о производительности, но я считаю, что разница между #pragma и include guard не будет ничем по сравнению с медлительностью синтаксического анализа грамматики C++. Это настоящая проблема. Попробуйте скомпилировать такое же количество файлов и строк с помощью компилятора C#, например, чтобы увидеть разницу.
В конце концов, использование охраны или прагмы не будет иметь никакого значения.
С помощью '#pragma once
"может не иметь никакого эффекта (он не поддерживается повсеместно - хотя он становится все более широко поддерживаемым), поэтому вам все равно нужно использовать код условной компиляции, и в этом случае зачем беспокоиться"#pragma once
"? Компилятор, вероятно, все равно его оптимизирует. Это зависит от вашей целевой платформы, хотя. Если все ваши цели поддерживают это, тогда продолжайте и используйте это - но это должно быть сознательное решение, потому что весь ад вырвется на свободу, если вы только используете прагму и затем портируете на компилятор, который не поддерживает это.
Преимущество в производительности заключается в том, что вам не нужно повторно открывать файл после прочтения #pragma. С защитой, компилятор должен открыть файл (который может быть дорогостоящим по времени), чтобы получить информацию, что он не должен снова включать его содержимое.
Это теория только потому, что некоторые компиляторы автоматически не открывают файлы, в которых не было никакого кода для чтения, для каждой единицы компиляции.
В любом случае, это относится не ко всем компиляторам, поэтому в идеале нужно избегать использования #pragma, поскольку кросс-платформенный код вообще не является стандартным / не имеет стандартизированного определения и эффекта. Тем не менее, на самом деле, это действительно лучше, чем охранники.
В конце концов, лучшее предложение, которое вы можете получить, чтобы быть уверенным в том, что ваш компилятор будет иметь лучшую скорость без необходимости проверять поведение каждого компилятора в этом случае, это использовать как pragma один раз, так и guard.
#ifndef NR_TEST_H
#define NR_TEST_H
#pragma once
#include "Thing.h"
namespace MyApp
{
// ...
}
#endif
Таким образом, вы получаете лучшее из обоих (кроссплатформенность и скорость компиляции справки).
Поскольку печатать дольше, я лично использую инструмент, который помогает генерировать все это очень мудрым способом (Visual Assist X).
Не всегда.
http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52566 имеет хороший пример двух файлов, которые должны быть включены в оба, но ошибочно считаются идентичными из-за идентичных меток времени и содержимого (не идентичное имя файла),
Я использую #ifndef / #define include guards, используя символы, включающие UUID, например:
#ifndef ARRAY__H_81945CB3_AEBB_471F_AC97_AB6C8B220314
#define ARRAY__H_81945CB3_AEBB_471F_AC97_AB6C8B220314 /* include guard */
#endif
Я всегда использовал редакторы, которые могли автоматически генерировать UUID. Это предотвращает конфликт имен с файлами с таким же базовым именем из других библиотек и определяет, находится ли один и тот же файл в нескольких местах внутри файловой системы.
Обратной стороной является увеличение размера таблицы, так как символы намного больше, но я пока не видел проблем с этим.
Сегодня старую школу включают охранников так же быстро, как и #pragma. Даже если компилятор не обрабатывает их специально, он все равно остановится, когда увидит, что #ifndef WHATEVER и WHATEVER определены. Открытие файла сегодня очень дешево. Даже если бы было улучшение, оно было бы порядка миллисекунд.
Я просто не использую #pragma один раз, так как это не приносит никакой пользы. Чтобы избежать столкновения с другими включенными охранниками, я использую что-то вроде: CI_APP_MODULE_FILE_H -> CI = Company Initials; APP = название приложения; остальное не требует пояснений.
Если мы используем msvc или Qt (до Qt 4.5), так как GCC(до 3.4), msvc оба поддерживают #pragma once
Я не вижу причин не использовать #pragma once
,
Имя исходного файла обычно совпадает с именем класса, и мы знаем, что когда-нибудь нам понадобится рефакторинг, чтобы переименовать имя класса, тогда нам пришлось изменить #include XXXX
Кроме того, так что я думаю, что руководство поддерживать #include xxxxx
это не умная работа. даже с расширением Visual Assist X поддерживать "xxxx" не обязательно.
Используя gcc 3.4 и 4.1 на очень больших деревьях (иногда используя distcc), мне еще предстоит увидеть какое-либо ускорение при использовании #pragma вместо или в сочетании со стандартными средствами защиты include.
Я действительно не понимаю, как это может сбить с толку старые версии gcc или даже другие компиляторы, так как реальной экономии нет. Я не пробовал все различные де-линтеры, но я готов поспорить, что это многих их запутает.
Я тоже хотел бы, чтобы он был принят на ранней стадии, но я вижу аргумент "Зачем нам это нужно, если ifndef работает совершенно нормально?". Учитывая много темных углов и сложностей С, включить охранников - одна из самых простых, самоочевидных вещей. Если у вас есть хотя бы небольшие знания о том, как работает препроцессор, они должны быть понятны.
Однако, если вы заметили значительное ускорение, обновите свой вопрос.
Дополнительное примечание для людей, думающих, что автоматическое одноразовое включение заголовочных файлов всегда желательно: я строю генераторы кода, использующие двойное или множественное включение заголовочных файлов с десятилетий. Специально для создания заглушек библиотеки протоколов мне очень удобно иметь чрезвычайно портативный и мощный генератор кода без дополнительных инструментов и языков. Я не единственный разработчик, использующий эту схему, как показывают эти блоги X-Macros. Это не было бы возможно сделать без отсутствующей автоматической охраны.
Основное отличие состоит в том, что компилятор должен был открыть файл заголовка, чтобы прочитать включенную защиту. Для сравнения, однажды прагма заставляет компилятор отслеживать файл и не выполнять какой-либо ввод-вывод файла, когда он сталкивается с другим включением для того же файла. Хотя это может показаться незначительным, это может легко масштабироваться с огромными проектами, особенно без хорошего заголовка, включая дисциплины.
Тем не менее, в наши дни компиляторы (включая GCC) достаточно умны, чтобы когда-то относиться к таким элементам защиты, как прагма. то есть они не открывают файл и избегают штрафа ввода-вывода файла.
В компиляторах, которые не поддерживают прагму, я видел ручные реализации, которые немного громоздки..
#ifdef FOO_H
#include "foo.h"
#endif
Лично мне нравится подход #pragma Once, так как он избегает хлопот с именами коллизий и потенциальных ошибок опечаток. Это также более элегантный код для сравнения. Тем не менее, для переносимого кода не должно быть вреда иметь оба, если компилятор не жалуется на это.