что такое pragma once c
once pragma
Specifies that the compiler includes the header file only once, when compiling a source code file.
Syntax
Remarks
The use of #pragma once can reduce build times, as the compiler won’t open and read the file again after the first #include of the file in the translation unit. It’s called the multiple-include optimization. It has an effect similar to the include guard idiom, which uses preprocessor macro definitions to prevent multiple inclusions of the contents of the file. It also helps to prevent violations of the one definition rule: the requirement that all templates, types, functions, and objects have no more than one definition in your code.
We recommend the #pragma once directive for new code because it doesn’t pollute the global namespace with a preprocessor symbol. It requires less typing, it’s less distracting, and it can’t cause symbol collisions. Symbol collisions are errors caused when different header files use the same preprocessor symbol as the guard value. It isn’t part of the C++ Standard, but it’s implemented portably by several common compilers.
There’s no advantage to use of both the include guard idiom and #pragma once in the same file. The compiler recognizes the include guard idiom, and implements the multiple-include optimization the same way as the #pragma once directive if no non-comment code or preprocessor directive comes before or after the standard form of the idiom:
We recommend the include guard idiom when code must be portable to compilers that don’t implement the #pragma once directive, to maintain consistency with existing code, or when the multiple-include optimization is impossible. It can occur in complex projects when file system aliasing or aliased include paths prevent the compiler from identifying identical include files by canonical path.
Урок №23. Header guards и #pragma once
Обновл. 11 Сен 2021 |
На этом уроке мы рассмотрим, что такое header guards и #pragma once в языке C++, а также зачем они нужны и как их правильно использовать.
Проблема дублирования объявлений
Как мы уже знаем из урока о предварительных объявлениях, идентификатор может иметь только одно объявление. Таким образом, программа с двумя объявлениями одной переменной получит ошибку компиляции:
То же самое касается и функций:
Рассмотрим следующую программу:
Эта, казалось бы, невинная программа, не скомпилируется! Проблема кроется в определении функции в файле 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 — это директивы условной компиляции, которые состоят из следующего:
2.11 – Защита заголовков
Проблема повторяющегося определения
В уроке «2.6 – Предварительные объявления и определения» мы отметили, что идентификатор переменной или функции может иметь только одно определение (правило одного определения). Таким образом, программа, которая определяет идентификатор переменной более одного раза, вызовет ошибку компиляции:
Точно так же программы, которые определяют функцию более одного раза, также вызовут ошибку компиляции:
Хотя эти программы легко исправить (удалить повторяющееся определение), с помощью заголовочных файлов довольно легко попасть в ситуацию, когда определение в заголовочный файл включается более одного раза. Это может произойти, если заголовочный файл включает с #include другой заголовочный файл (что является обычным явлением).
Рассмотрим следующий академический пример:
Таким образом, после разрешения всех директив #include файл main.cpp будет выглядеть так:
Защита заголовка
Хорошей новостью является то, что мы можем избежать указанной выше проблемы с помощью механизма, называемого защитой заголовка (или защитой включения). Защита заголовка – это директивы условной компиляции, которые имеют следующую форму:
Все ваши заголовочные файлы должны иметь защиту заголовков. Имя SOME_UNIQUE_NAME_HERE может быть любым, но по соглашению устанавливается равным полному имени заголовочного файла, набранному заглавными буквами, с использованием подчеркивания вместо пробелов и знаков препинания. Например, у square.h будет защита заголовка будет следующей:
Даже заголовочные файлы стандартной библиотеки используют защиту заголовков. Если бы вы взглянули на заголовочный файл iostream из Visual Studio, вы бы увидели:
Для продвинутых читателей
Обновление нашего предыдущего примера с помощью защиты заголовков
После того, как препроцессор разрешит все включения, эта программа будет выглядеть так:
Как видно из примера, второе включение содержимого square.h (из geometry.h ) игнорируется потому, что SQUARE_H уже был определен при первом включении. Следовательно, функция getSquareSides включается только один раз.
Защита заголовков не препятствует одиночным включениям заголовка в разные файлы исходного кода.
Обратите внимание, что цель защиты заголовков – предотвратить получение файлом исходного кода более одной копии защищенного заголовка. По замыслу, защита заголовков не препятствует включению данного заголовочного файла (однократно) в отдельные исходные файлы. Это также может вызвать непредвиденные проблемы. Рассмотрим следующую возможность:
Разве мы не можем просто избежать определений в файлах заголовков?
Обычно мы советуем вам не включать определения функций в заголовки. Итак, вам может быть интересно, зачем вам включать защиту заголовков, если она защищают вас от чего-то, чего вы не должны делать.
В будущем мы покажем вам довольно много случаев, когда в файл заголовка необходимо поместить определения, не являющиеся функциями. Например, C++ позволяет вам создавать свои собственные типы. Эти пользовательские типы обычно определяются в файлах заголовков, чтобы эти определения можно было распространить на исходные файлы, которые должны их использовать. Без защиты заголовков ваши исходные файлы могут иметь несколько идентичных копий этих определений, что приведет к ошибке компиляции повторяющихся определений.
Таким образом, хотя на данном этапе этой серии обучающих статей не обязательно иметь защиту заголовков, но мы вырабатываем хорошие привычки, чтобы вам не приходилось отказываться от вредных привычек позже.
#pragma once
Многие компиляторы поддерживают более простую альтернативную форму защиты заголовков с помощью директивы #pragma :
#pragma когда-то служила той же цели, что и защита заголовков, а ее дополнительное преимущество заключается в том, что она короче и менее подвержена ошибкам.
Однако #pragma once не является официальной частью языка C++, и не все компиляторы поддерживают ее (хотя большинство современных компиляторов поддерживает).
В целях совместимости мы рекомендуем придерживаться традиционной защиты заголовков. Она не требуют много работы и гарантированно поддерживаются всеми компиляторами.
Резюме
Защита заголовков предназначена для того, чтобы содержимое заданного заголовочного файла не копировалось более одного раза в любой отдельный файл, чтобы предотвратить дублирование определений.
Обратите внимание, что дублирование объявлений – это нормально, поскольку объявление может быть объявлено несколько раз без инцидентов, но даже если ваш заголовочный файл состоит только из объявлений (без определений), всё равно рекомендуется включать защиту заголовков.
Обратите внимание, что защита заголовков не предотвращает копирование содержимого заголовочного файла (один раз) в отдельные файлы проекта. Это хорошо потому, что нам часто нужно ссылаться на содержимое заданного заголовка из разных файлов проекта.
Небольшой тест
Вопрос 1
Добавьте защиту заголовка в этот заголовочный файл:
Pragma once
В языках программирования Си и C++ #pragma once — нестандартная, но широко распространенная препроцессорная директива, разработанная для контроля за тем, чтобы конкретный исходный файл при компиляции подключался строго один раз. То есть, #pragma once применяется для тех же целей, что и include guard, но требует меньше кода и не допускает возможности коллизии имён. В наборе компиляторов GCC до версии 3.4 считалась устаревшей и для применения не рекомендовалась. [1] Однако из-за широкого применения это решение было изменено. [2]
В статье об include guard приводится пример ситуации, в которой нужно использовать тот или иной метод. Выходом является использование include guard, приведенное там же; Вариантом использования #pragma once может быть:
Содержание
Поддержка компиляторов
Компилятор | #pragma once |
---|---|
Clang | Да [3] |
Comeau C/C++ | Да [4] |
C++Builder XE3 | Да [5] |
Digital Mars C++ | Да [6] |
GCC | Да [7] (с версии 3.4 [8] ) |
HP C/aC++ | Да [9] (по крайней мере с версии A.06.12) |
IBM XL C/C++ | Да [10] (с версии 13.1.1) |
Intel C++ Compiler | Да [11] |
Microsoft Visual C++ | Да [12] (с версии 4.2) |
Pelles C | Да [13] |
ARM DS-5 | Да [14] |
IAR C/C++ | Да [15] |
Solaris Studio C/C++ | Нет [16] [17] |
Достоинства и недостатки
С другой стороны, некоторые компиляторы, как например, GCC, также используют специальный код для распознавания и оптимизации обработки include guard. [1]
Можно использовать обе команды, #pragma once и include guards, для написания переносимого кода, что также может принести выгоду от применения #pragma once при оптимизации (если компилятор её поддерживает):
Напишите отзыв о статье «Pragma once»
Примечания
Дополнительные источники
Отрывок, характеризующий Pragma once
В пятницу Ростовы должны были ехать в деревню, а граф в среду поехал с покупщиком в свою подмосковную.
В день отъезда графа, Соня с Наташей были званы на большой обед к Карагиным, и Марья Дмитриевна повезла их. На обеде этом Наташа опять встретилась с Анатолем, и Соня заметила, что Наташа говорила с ним что то, желая не быть услышанной, и всё время обеда была еще более взволнована, чем прежде. Когда они вернулись домой, Наташа начала первая с Соней то объяснение, которого ждала ее подруга.
– Вот ты, Соня, говорила разные глупости про него, – начала Наташа кротким голосом, тем голосом, которым говорят дети, когда хотят, чтобы их похвалили. – Мы объяснились с ним нынче.
– Ну, что же, что? Ну что ж он сказал? Наташа, как я рада, что ты не сердишься на меня. Говори мне всё, всю правду. Что же он сказал?
Наташа задумалась.
– Ах Соня, если бы ты знала его так, как я! Он сказал… Он спрашивал меня о том, как я обещала Болконскому. Он обрадовался, что от меня зависит отказать ему.
Электронная библиотека
Директивы #pragma позволяют использовать специфичные для конкретных реализаций директивы в форме
При помощи директивы #pragma язык C++ позволяет определить любые желаемые директивы, не обращаясь для этого к другим, поддерживающим их компиляторам. Если компилятор не поддерживает данное имя директивы, то он просто игнорирует директиву #pragma, не выдавая при этом никаких сообщений об ошибках или предупреждений.
Язык C++ поддерживает следующие директивы #pragma:
Директива #pragma alignment выдает сообщение о текущем выравнивании данных и размере enum-типов.
Директива #pragma argsused допустима только между определениями функций и действует только на следующую функцию. Она отменяет сообщение уровня предупреждения «Parameter name is never used in function имя-функции» – («имя параметра нигде не используется в функции имя-функции»).
Директива #pragma checkoption проверяет, установлены ли опции, указанные в директиве. Если они не установлены, выдается сообщение об ошибке. Синтаксис директивы #pragma checkoption следующий:
#pragma checkoption строка_ опций
Директива #pragma codeseg позволяет указать имя или класс сегмента либо группу, где будут размещаться функции. Если все опции директивы опущены, используется сегмент кода по умолчанию. Синтаксис директивы следующий:
#pragma codeseg [имя_сегмента] [«класс«] [группа]
Директива #pragma comment записывает строку-комментарий в объектный или исполняемый файл. Синтаксис директивы следующий:
Директивы #pragma exit и #pagma startup позволяют программе задать функцию (функции), которая должна вызываться либо при загрузке программы (перед вызовом main), либо при выходе из программы (непосредственно перед выходом из программы через _exit). Синтаксис этих директив следующий:
#pragma exit имя-функции
#pragma startup имя-функции
Параметр должен являться целым числом в диапазоне от 64 до 255. Старшим приоритетом является 0 (приоритеты от 0 до 63 используются библиотеками языка С/С++ и не должны использоваться пользователем). Функции со старшими приоритетами вызываются первыми при загрузке программы и последними при выходе из нее. Если приоритет не задан, то по умолчанию он равен 100.
/* приоритет 64 —> вызывается при загрузке первой */
printf(«Wrapping up execution.n»);
#pragma exit exitFunc
/* приоритет по умолчанию равен 100 */
Отметим, что функция, имя которой используется в директиве #pragma startup или #pragma exit, должна быть определена (или объявлена) до того, как встретится соответствующая строка с директивой #pragma.
Директива #pragma hdrfile специфицирует имя файла, в котором хранятся прекомпиляционные заголовки.
Её синтаксис следующий:
#pragma hdrfile «filename.SYM»
Если программист не использует прекомпиляционные заголовки, то эта директива не будет эффективна.
Директива #pragma hdrstop запрещает включать дальнейшую информацию в файл прекомпилируемых заголовков. Программист может использовать ее, чтобы уменьшить количество дискового пространства, используемого прекомпиляционными заголовками.
Директива #pragma inline сообщает компилятору, что программа содержит встроенные ассемблерные коды. Синтаксис её следующий:
Директива #pragma link заставляет компоновщик подключить к исполняемому модулю указанный объектный файл. Синтаксис её следующий:
Директива #pragma message выдает сообщение при компиляции. Синтаксис её следующий:
Директива #pragma obsolete выдает предупреждение о том, что данная функция является устаревшей (если имеются обращения к ней). Директивой можно информировать других программистов, что вы усовершенствовали свой код и предусмотрели новую функцию для данной задачи. Синтаксис её следующий:
Директива #pragma option используется для включения опций компилятора командной строки в код вашей программы. В одной директиве может находиться любое число опций. Опции компилятора командной строки могут быть любыми, но имеется ограничение. Синтаксис этой директивы следующий:
Директива #pragma option позволяет указать необходимые опции командной строки прямо в коде программы. Форма option push сначала сохраняет текущие установки в стеке компилятора; форма option pop выталкивает из стека последний набор опций.
Директива #pragma pack задает выравнивание данных в памяти. Синтаксис её следующий:
#pragma pack (push, n)
Директива #pragma package управляет порядком инициализации модулей в пакетах C++Builder. По умолчанию включается в начало каждого автоматически создаваемого модуля. Синтаксис её следующий:
#pragma package(smart init)
#pragma package(smart init, weak)
Директива #pragma resource помечает текущий файл как модуль формы. В текущем каталоге должны присутствовать соответствующий dfm-файл и заголовок. Всеми этими файлами IDE управляет автоматически. Синтаксис директивы #pragma resource следующий:
Директива #pragma saveregs гарантирует, что при входе в функцию huge значения всех регистров останутся без изменений. Данная директива иногда бывает нужна для интерфейса с кодами на языке ассемблера. Директива должна находиться непосредственно перед определением функции. Ее действие распространяется только на данную функцию.
Директива #pragma warn позволяет переопределять конкретные опции командной строки или управлять опцией Display Warnings в диалоговом окне Options | Compiler | Messages). Например, если в вашем исходном коде имеются директивы
то выдача предупреждения xxx будет разрешена (даже если в меню Options | Compiler | Messages она была переведена в состояние off), предупреждения yyy запрещены, а статус выдачи сообщения zzz будет восстановлен в то состояние, которое было к моменту начала компиляции файла.
Существует также специальная директива, указывающая препроцессору, что файл должен быть включен не более одного раза. Эта директива называется ‘#pragma once’. Она использовалась в дополнение к директиве ‘#ifndef’ и в настоящее время устарела и не должна применяться.
Срочно?
Закажи у профессионала, через форму заявки
8 (800) 100-77-13 с 7.00 до 22.00