#pragma один раз против включить охранников?

Я работаю над кодовой базой, которая, как известно, работает только в windows и компилируется в Visual Studio (она тесно интегрируется с excel, поэтому никуда не денется). Мне интересно, должен ли я пойти с традиционными охранниками или использовать #pragma once для нашего кода. Я бы подумал, что компилятор имеет дело с #pragma once даст более быстрые компиляции и менее подвержен ошибкам при копировании и вставке. Это также немного менее уродливо ;)

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

13 ответов


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

#pragma once менее склонен к ошибкам,и это меньше кода для ввода.

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

I предпочитаю использовать #pragma once.

посмотреть этот статья Википедии о возможности использования обоих.


Я просто хотел добавить к этому обсуждению, что я просто компилирую на VS и GCC и использовал include guards. Теперь я переключился на #pragma once, и единственная причина для меня - это не производительность или переносимость или стандарт, поскольку мне все равно, что является стандартным, пока VS и GCC поддерживают его, и это так:

#pragma once уменьшает возможности для ошибок.

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


#pragma once и unfixable ошибки. Он никогда не должен использоваться.

если #include путь поиска достаточно сложен, компилятор может быть не в состоянии определить разницу между двумя заголовками с одинаковым базовым именем (например,a/foo.h и b/foo.h), поэтому #pragma once в одном из них будет подавлять и. Он также может быть не в состоянии сказать, что два разных родственника включают (например,#include "foo.h" и #include "../a/foo.h" обратитесь к тому же файлу, так что #pragma once будет не подавить избыточный include, когда он должен был.

это также влияет на способность компилятора избегать перечитывания файлов с помощью #ifndef охранники, но это просто оптимизация. С #ifndef guards, компилятор может безопасно читать любой файл, это не обязательно он уже видел; если это неправильно, он просто должен сделать дополнительную работу. Пока два заголовка не определяют один и тот же макрос guard, код будет компилироваться, как ожидалось. А если два заголовка do определить тот же макрос guard, программист может войти и изменить один из них.

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

(короткая версия, почему это unfixable это ни Unix, ни API файловой системы Windows не предлагают никакого механизма, который гарантии чтобы сообщить вам, относятся ли два абсолютных пути к одному и тому же файлу. Если у вас сложилось впечатление, что для этого можно использовать номера индексов, извините, вы ошибаетесь.)

(историческое Примечание: единственная причина, по которой я не разорвал #pragma once и #import из GCC, когда у меня были полномочия сделать это, ~12 лет назад были системные заголовки Apple, полагающиеся на них. В оглядываясь назад, это не должно было остановить меня.)

(так как это теперь появилось дважды в потоке комментариев: разработчики GCC приложили немало усилий, чтобы сделать #pragma once как можно надежнее; см. отчет об ошибке GCC 11569. Однако реализация в текущих версиях GCC не в правдоподобных условиях, таких как строительство ферм, страдающих от перекоса часов. Я не знаю, как выглядит реализация любого другого компилятора, но я не ожидал бы, что кто-то сделал лучше.)


до дня #pragma once становится стандартным (это не является приоритетом для будущих стандартов), я предлагаю вам использовать его и использовать guards, таким образом:

#ifndef BLAH_H
#define BLAH_H
#pragma once

// ...

#endif

причины :

  • #pragma once не является стандартным, поэтому возможно, что некоторые компиляторы не предоставляют функциональность. Тем не менее, все основные компиляторы поддерживают его. Если компилятор этого не знает, по крайней мере, он будет проигнорирован.
  • поскольку нет стандартного поведения для #pragma once, вы не следует предполагать, что поведение будет одинаковым для всех компиляторов. Охранники гарантируют, по крайней мере, что основное предположение одинаково для всех компиляторов, которые, по крайней мере, реализуют необходимые инструкции препроцессора для охранников.
  • на большинстве компиляторов, #pragma once ускорит компиляцию (одного cpp), потому что компилятор не откроет файл, содержащий эту инструкцию. Таким образом, наличие его в файле может помочь или нет, в зависимости от компилятора. Я слышал, что g++ может сделать то же самое оптимизация при обнаружении охранников, но это должно быть подтверждено.

используя два вместе, вы получаете лучшее от каждого компилятора для этого.

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

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


С точки зрения тестировщика

#pragma once короче, чем include guard, менее подвержен ошибкам, поддерживается большинством компиляторов, а некоторые говорят, что он компилируется быстрее (что неверно [больше]).

но я все же предлагаю вам пойти со стандартом #ifndef включают в себя охранников.

почему #ifndef?

рассмотрим надуманную иерархию классов, как это, где каждый из классов A, B и C живет внутри своего собственного файл:

а.ч

#ifndef A_H
#define A_H

class A {
public:
  // some virtual functions
};

#endif

б.ч

#ifndef B_H
#define B_H

#include "a.h"

class B : public A {
public:
  // some functions
};

#endif

Си.ч

#ifndef C_H
#define C_H

#include "b.h"

class C : public B {
public:
  // some functions
};

#endif

теперь предположим, что вы пишете тесты для своих классов, и вам нужно имитировать поведение действительно сложного класса B. Один из способов сделать это - написать макет класс например Гугл макет и поместите его в каталог mocks/b.h. Обратите внимание, что имя класса не изменилось, но оно хранится только внутри другого справочник. Но самое главное, что Include guard назван точно так же, как и в исходном файле b.h.

издевается/б.ч

#ifndef B_H
#define B_H

#include "a.h"
#include "gmock/gmock.h"

class B : public A {
public:
  // some mocks functions
  MOCK_METHOD0(SomeMethod, void());
};

#endif

в чем выгода?

С таким подходом можно глумиться поведение класс B не касаясь исходного класса или говорит C об этом. Все, что вам нужно сделать, это поместить каталог mocks/ в пути включения вашего complier.

почему это нельзя сделать с #pragma once?

если бы вы воспользовались #pragma once, вы получите столкновение имен, потому что оно не может защитить вас от определения класса B дважды, один раз оригинал и один раз издевались версии.


Если вы уверены, что никогда не будете использовать этот код в компиляторе, который его не поддерживает (Windows / VS, GCC и Clang являются примерами компиляторов, которые do support it), то вы, безусловно, можете использовать #pragma один раз без забот.

вы также можете просто использовать оба (см. пример ниже), чтобы получить переносимость и ускорение компиляции в совместимых системах

#pragma once
#ifndef _HEADER_H_
#define _HEADER_H_

...

#endif

Я вообще не заморачиваюсь с #pragma once поскольку мой код иногда должен компилироваться с чем-то другим, кроме MSVC или GCC (компиляторы для встроенных систем не всегда имеют #pragma).

поэтому я должен использовать #include guards в любом случае. Я мог бы также использовать #pragma once Как показывают некоторые ответы, но, похоже, не так много причин, и это часто вызывает ненужные предупреждения на компиляторах, которые его не поддерживают.

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

в любом случае, мне просто проще использовать #include guards, которые будут работать везде и не беспокоиться об этом дальше.


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

для теста я автоматически сгенерировал 500 заголовочных файлов со сложными взаимозависимостями, и имел это #includes их всех. Я провел тест тремя способами, один раз с помощью just #ifndef, когда с #pragma once и сразу с обоими. Я выполнил тест на довольно современной системе (2014 MacBook Pro под управлением OSX, используя комплектный Clang XCode, с внутренним SSD).

во-первых, тестовый код:

#include <stdio.h>

//#define IFNDEF_GUARD
//#define PRAGMA_ONCE

int main(void)
{
    int i, j;
    FILE* fp;

    for (i = 0; i < 500; i++) {
        char fname[100];

        snprintf(fname, 100, "include%d.h", i);
        fp = fopen(fname, "w");

#ifdef IFNDEF_GUARD
        fprintf(fp, "#ifndef _INCLUDE%d_H\n#define _INCLUDE%d_H\n", i, i);
#endif
#ifdef PRAGMA_ONCE
        fprintf(fp, "#pragma once\n");
#endif


        for (j = 0; j < i; j++) {
            fprintf(fp, "#include \"include%d.h\"\n", j);
        }

        fprintf(fp, "int foo%d(void) { return %d; }\n", i, i);

#ifdef IFNDEF_GUARD
        fprintf(fp, "#endif\n");
#endif

        fclose(fp);
    }

    fp = fopen("main.c", "w");
    for (int i = 0; i < 100; i++) {
        fprintf(fp, "#include \"include%d.h\"\n", i);
    }
    fprintf(fp, "int main(void){int n;");
    for (int i = 0; i < 100; i++) {
        fprintf(fp, "n += foo%d();\n", i);
    }
    fprintf(fp, "return n;}");
    fclose(fp);
    return 0;
}

и теперь, мои различные тестовые прогоны:

folio[~/Desktop/pragma] fluffy$ gcc pragma.c -DIFNDEF_GUARD
folio[~/Desktop/pragma] fluffy$ ./a.out 
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.164s
user    0m0.105s
sys 0m0.041s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.140s
user    0m0.097s
sys 0m0.018s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.193s
user    0m0.143s
sys 0m0.024s
folio[~/Desktop/pragma] fluffy$ gcc pragma.c -DPRAGMA_ONCE
folio[~/Desktop/pragma] fluffy$ ./a.out 
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.153s
user    0m0.101s
sys 0m0.031s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.170s
user    0m0.109s
sys 0m0.033s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.155s
user    0m0.105s
sys 0m0.027s
folio[~/Desktop/pragma] fluffy$ gcc pragma.c -DPRAGMA_ONCE -DIFNDEF_GUARD
folio[~/Desktop/pragma] fluffy$ ./a.out 
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.153s
user    0m0.101s
sys 0m0.027s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.181s
user    0m0.133s
sys 0m0.020s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.167s
user    0m0.119s
sys 0m0.021s
folio[~/Desktop/pragma] fluffy$ gcc --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/usr/include/c++/4.2.1
Apple LLVM version 8.1.0 (clang-802.0.42)
Target: x86_64-apple-darwin17.0.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

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

дело в том,#ifndef стандартизирован со стандартным поведением, тогда как #pragma once не и #ifndef также обрабатывает странные файловые системы и угловые пути поиска, тогда как #pragma once может очень запутаться в некоторых вещах, что приводит к неправильному поведению, которое программист не контролирует. Основная проблема, с #ifndef программисты выбирают плохие имена для своих охранников (с коллизиями имен и т. д.), И даже тогда для потребителя API вполне возможно переопределить эти плохие имена, используя #undef - не идеальное решение, возможно, но это возможно, а #pragma once есть нет регресса, если компилятор ошибочно отбраковывает #include.

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

редактировать: благодаря обратной связи от @LightnessRacesInOrbit я увеличил количество заголовочных файлов и изменил тест, чтобы запустить только шаг препроцессора, исключив любое небольшое количество времени, добавляемое в процессом компиляции и ссылки (который был тривиальным раньше и несуществующим сейчас). Как и ожидалось, разница примерно такая же.


Я думаю, что первое, что вы должны сделать, это проверить, действительно ли это будет иметь значение, т. е. сначала вы должны проверить производительность. Один из поисков в google вырвало этой.

на странице результатов столбцы sligthly выключены для меня, но ясно, что по крайней мере до VC6 microsoft не реализовывала оптимизацию include guard, которую использовали другие инструменты. Где включить охранник внутреннего потребовалось 50 раз дольше по сравнению где Include guard был внешним (внешние Include guards по крайней мере так же хороши, как #pragma). Но давайте рассмотрим возможное влияние этого:

согласно представленным таблицам, время открытия include и проверки в 50 раз больше, чем у эквивалента #pragma. Но фактическое время для этого было измерено в 1 микросекунде на файл еще в 1999 году!

Итак, сколько дубликатов заголовков будет иметь один TU? Это зависит от вашего стиля, но если мы скажем, что в среднем У TU есть 100 дубликатов, тогда в 1999 году мы потенциально платим 100 микросекунд за TU. С улучшениями HDD это, вероятно, значительно ниже сейчас, но даже тогда с предварительно скомпилированными заголовками и правильным отслеживанием зависимостей общая совокупная стоимость этого для проекта почти наверняка является инсайгифицирующей частью вашего времени сборки.

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

нет причин, по которым Microsoft не могла бы реализовать оптимизацию include guard таким же образом, как GCC и любой другой компилятор (на самом деле может ли кто-нибудь подтвердить, что их более поздние версии реализуют это?). ИМХО,#pragma once делает очень мало, кроме как ограничивает ваш выбор альтернативного компилятора.


здесь вопрос, к которому Я ответил::

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

Я добавляю ответ и здесь, Если кто-то наткнется на этот вопрос, а не на другой.


#pragma once позволяет компилятору полностью пропустить файл, когда он происходит снова-вместо разбора файла, пока он не достигнет # include guards.

таким образом, семантика немного отличается, но они идентичны, если они используются так, как они предназначены для использования.

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

когда вы ограничиваете свои платформы, скажем, "мейнстрим-компиляторы на рабочем столе", вы можете безопасно опустить #include guards, но я тоже чувствую себя неловко.

OT: если у вас есть другие советы/опыт для обмена на ускорение сборки, мне было бы любопытно.


для тех, кто хотел бы использовать #pragma один раз и включить guards вместе: Если вы не используете MSVC, то вы не получите много оптимизации от #pragma один раз.

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

здесь подробное обсуждение с примерами использования #pragma once.


на объяснения Конрад Кляйн выше.

краткое описание:

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

теперь компилятор ищет этот фрагмент кода в начале файла и пропускает его из включения (если он уже включен один раз). Этот определенно сократит время компиляции (в среднем и в огромной системе). Однако в случае mocks / тестовой среды реализация тестовых случаев будет затруднена из-за циклических зависимостей etc.

  • теперь, когда мы используем #ifndef XYZ_H для заголовков больше ответственности разработчиков за поддержание зависимости заголовков. Это означает, что всякий раз, когда из-за какого-либо нового файла заголовка существует возможность циклической зависимости, компилятор просто помечает некоторые "undefined .." сообщения об ошибках во время компиляции, и пользователь должен проверить логическое соединение/поток сущностей и исправить неправильные включения.

это определенно добавит к времени компиляции (как необходимо исправить и повторно запустить). Кроме того, как он работает на основе включения файла, основанного на "xyz_h" defined-state, и по-прежнему жалуется, если не может получить все определения.

поэтому, чтобы избежать подобных ситуаций, мы должны использовать, as;

#pragma once
#ifndef XYZ_H
#define XYZ_H
...
#endif

т. е. комбинация обоих.