Czy #pragma, gdy raz jest bezpieczny, obejmuje strażników?

310

Czytałem, że podczas korzystania z programu istnieje pewna optymalizacja kompilatora, #pragma oncektóra może spowodować szybszą kompilację. Rozumiem, że jest to niestandardowe, a zatem może powodować problem ze zgodnością między platformami.

Czy jest to obsługiwane przez większość nowoczesnych kompilatorów na platformach innych niż Windows (gcc)?

Chcę uniknąć problemów z kompilacją platformy, ale chcę również uniknąć dodatkowej pracy strażników rezerwowych:

#pragma once
#ifndef HEADER_H
#define HEADER_H

...

#endif // HEADER_H

Czy powinienem się martwić? Czy powinienem poświęcić na to więcej energii mentalnej?

Ryan Emerle
źródło
3
Po zadaniu podobnego pytania okazało się, że #pragma oncewydaje się, że pozwala uniknąć niektórych problemów z widokiem klas w VS 2008. Właśnie pozbywam się strażników dołączających i zastępuję je z #pragma oncetego powodu.
SmacL

Odpowiedzi:

188

Używanie #pragma oncepowinno działać na każdym nowoczesnym kompilatorze, ale nie widzę żadnego powodu, aby nie używać standardowej funkcji #ifndefosłony. Działa dobrze. Jedynym zastrzeżeniem jest to, że GCC nie obsługiwał wersji#pragma once wcześniejszej niż 3.4 .

Przekonałem się również, że przynajmniej w GCC rozpoznaje standard #ifndefobejmuje ochronę i optymalizuje ją , więc nie powinna być dużo wolniejsza niż #pragma once.

Zifre
źródło
12
Nie powinno być wcale wolniej (w każdym razie z GCC).
Jason Coco
54
To nie jest zaimplementowane w ten sposób. Zamiast tego, jeśli plik zaczyna się od #ifndef po raz pierwszy, a kończy na #endif, gcc pamięta go i zawsze pomija to w przyszłości, nawet nie zawracając sobie głowy otwieraniem pliku.
Jason Coco,
10
#pragma oncejest generalnie szybszy, ponieważ plik nie jest wstępnie przetwarzany. ifndef/define/endifi tak wymaga wstępnego przetwarzania, ponieważ po tym bloku możesz mieć coś do kompilacji (teoretycznie)
Andrey
13
Dokumenty
Adrian
38
Aby użyć opcji uwzględnienia strażników, istnieje dodatkowy wymóg, że należy zdefiniować nowy symbol, taki jak #ifndef FOO_BAR_Hzwykle dla pliku takiego jak „foo_bar.h”. Jeśli później zmienisz nazwę tego pliku, czy powinieneś odpowiednio dostosować osłony dołączające, aby były zgodne z tą konwencją? Ponadto, jeśli masz dwa różne foo_bar.h w dwóch różnych miejscach w drzewie kodu, musisz pomyśleć o dwóch różnych symbolach dla każdego z nich. Krótka odpowiedź brzmi: #pragma oncejeśli naprawdę potrzebujesz skompilować w środowisku, które go nie obsługuje, to dodaj i dodaj osłony dla tego środowiska.
Brandin
327

#pragma once ma jedną wadę (inną niż niestandardowa), a więc jeśli masz ten sam plik w różnych lokalizacjach (mamy to, ponieważ nasz system kompilacji kopiuje pliki), to kompilator pomyśli, że są to różne pliki.

Motti
źródło
36
Ale możesz również mieć dwa pliki o tej samej nazwie w różnych lokalizacjach, bez konieczności tworzenia różnych # NAZW, które często mają postać HEADERFILENAME_H
Vargas
69
Możesz także mieć dwa lub więcej plików z tym samym #define WHATEVER, co nie powoduje końca zabawy, dlatego wolę jednorazowe użycie pragmy.
Chris Huang-Leaver,
107
Nie przekonuje ... Zmień system kompilacji na taki, który nie kopiuje plików, ale zamiast tego używa dowiązań symbolicznych lub dołącza ten sam plik tylko z jednego miejsca w każdej jednostce tłumaczeniowej. Wygląda na to, że twoja infrastruktura to bałagan, który musi zostać zreorganizowany.
Jakow Galka
3
A jeśli masz różne pliki o tej samej nazwie w różnych katalogach, podejście #ifdef uzna, że ​​są to ten sam plik. Jest więc jedna wada, a druga wada.
rxantos
3
@rxantos, jeśli pliki się różnią, #ifdefwartość makra może się również różnić.
Motti
63

Chciałbym #pragma once(lub coś w tym rodzaju) być w standardzie. Uwzględnij strażników to nie jest wielka sprawa (ale wydaje się, że jest to trochę trudne do wytłumaczenia dla osób uczących się języka), ale wydaje się to niewielką uciążliwością, której można było uniknąć.

W rzeczywistości, ponieważ w 99,98% przypadków #pragma oncezachowanie jest pożądanym zachowaniem, byłoby miło, gdyby kompilator automatycznie obsługiwał wielokrotne włączanie nagłówka za pomocą#pragma coś” lub „coś”.

Ale mamy to, co mamy (z wyjątkiem tego, że możesz nie mieć #pragma once).

Michael Burr
źródło
48
To, czego naprawdę chcę, to standardowa #importdyrektywa.
John
10
Nadchodzi standardowa dyrektywa importowa: isocpp.org/blog/2012/11/... Ale jeszcze nie tutaj. Mocno to popieram.
AHelps
6
@AHelps Vaporware. Minęło już prawie pięć lat. Może w 2023 r. Wrócisz do tego komentarza i powiesz „Tak ci powiedziałem”.
doug65536,
To nie jest vaporware, ale tylko na etapie specyfikacji technicznej. Moduły są implementowane w Visual Studio 2015 ( blogs.msdn.microsoft.com/vcblog/2015/12/03/… ) oraz w clang ( clang.llvm.org/docs/Modules.html ). I to import, nie #import.
AHelps
Powinien przejść do C ++ 20.
Ionoclast Brigham
36

Nie wiem o żadnych korzyściach z wydajności, ale na pewno działa. Używam go we wszystkich moich projektach C ++ (oczywiście przy użyciu kompilatora MS). Uważam, że jest bardziej skuteczny niż używanie

#ifndef HEADERNAME_H
#define HEADERNAME_H
...
#endif

Wykonuje tę samą pracę i nie zapełnia preprocesora dodatkowymi makrami.

GCC obsługuje #pragma onceoficjalnie od wersji 3.4 .

JaredPar
źródło
25

GCC obsługuje #pragma onceod wersji 3.4, patrz http://en.wikipedia.org/wiki/Pragma_once, aby uzyskać dalszą obsługę kompilatora.

Wielką zaletą, którą widzę przy użyciu #pragma once zamiast włączania strażników, jest unikanie błędów kopiowania / wklejania.

Spójrzmy prawdzie w oczy: większość z nas z trudem rozpoczyna tworzenie nowego pliku nagłówka od zera, ale po prostu kopiuje istniejący i modyfikuje go zgodnie z naszymi potrzebami. Znacznie łatwiej jest stworzyć działający szablon, używając #pragma oncezamiast uwzględniać strażników. Im mniej muszę modyfikować szablon, tym mniej prawdopodobne jest, że popełniam błędy. Posiadanie tego samego zabezpieczenia w różnych plikach prowadzi do dziwnych błędów kompilatora i zajmuje trochę czasu, aby dowiedzieć się, co poszło nie tak.

TL; DR: #pragma oncejest łatwiejszy w użyciu.

uceumern
źródło
11

Używam go i jestem z niego zadowolony, ponieważ muszę pisać znacznie mniej, aby utworzyć nowy nagłówek. Działa mi dobrze na trzech platformach: Windows, Mac i Linux.

Nie mam żadnych informacji o wydajności, ale wierzę, że różnica między #pragma i wartownikiem dołączającym nie będzie niczym w porównaniu z powolnością analizowania gramatyki C ++. To jest prawdziwy problem. Spróbuj na przykład skompilować tę samą liczbę plików i linii za pomocą kompilatora C #, aby zobaczyć różnicę.

W końcu użycie strażnika lub pragmy nie będzie miało żadnego znaczenia.

Edwin Jarvis
źródło
Raz nie lubię #pragmy, ale doceniam to, że zwracasz uwagę na względne korzyści ... Analiza składni w C ++ jest znacznie droższa niż cokolwiek innego w „normalnym” środowisku operacyjnym. Nikt nie kompiluje ze zdalnego systemu plików, jeśli czasy kompilacji stanowią problem.
Tom
1
Re C ++ spowolnienie analizy w porównaniu do C #. W C # nie musisz analizować (dosłownie) tysięcy LOC plików nagłówkowych (iostream, ktoś?) Dla każdego małego pliku C ++. Użyj jednak wstępnie skompilowanych nagłówków, aby zmniejszyć ten problem
Eli Bendersky,
11

Użycie „ #pragma once” może nie przynieść żadnego efektu (nie jest obsługiwane wszędzie - chociaż jest coraz częściej obsługiwane), więc i tak musisz użyć kodu kompilacji warunkowej, w takim przypadku, po co zawracać sobie głowę „ #pragma once”? Kompilator prawdopodobnie i tak go zoptymalizuje. Zależy to jednak od twoich platform docelowych. Jeśli wszystkie twoje cele go obsługują, to idź dalej i skorzystaj z niego - ale powinna to być świadoma decyzja, ponieważ całe piekło rozpęta się, jeśli użyjesz tylko pragmy, a następnie przełączysz się na kompilator, który jej nie obsługuje.

Jonathan Leffler
źródło
1
Nie zgadzam się, że i tak musisz wspierać strażników. Jeśli użyjesz #pragma raz (lub strażników), dzieje się tak, ponieważ powoduje to pewne konflikty bez nich. Więc jeśli nie jest obsługiwane przez twoje narzędzie łańcuchowe, projekt po prostu się nie skompiluje i jesteś dokładnie w takiej samej sytuacji, jak gdy chcesz skompilować trochę ansi C na starym kompilatorze K&R. Musisz tylko zdobyć bardziej aktualne narzędzie łańcuchowe lub zmienić kod, aby dodać strażników. Całe piekło byłoby, gdyby program się kompilował, ale nie działał.
kriss
5

Korzyścią z wydajności jest to, że nie trzeba ponownie otwierać pliku po przeczytaniu #pragmy. W przypadku strażników kompilator musi otworzyć plik (co może być kosztowne w czasie), aby uzyskać informacje, że nie powinien ponownie zawierać jego zawartości.

Jest to teoria tylko dlatego, że niektóre kompilatory nie będą automatycznie otwierać plików, w których nie ma kodu odczytu dla każdej jednostki kompilacji.

W każdym razie nie jest tak w przypadku wszystkich kompilatorów, więc najlepiej #pragma, której należy kiedyś unikać w przypadku kodu wieloplatformowego, nie ma w ogóle standardu / nie ma znormalizowanej definicji i efektu. Jednak praktycznie jest to naprawdę lepsze niż strażnicy.

Ostatecznie lepszym pomysłem na uzyskanie najlepszej prędkości kompilatora bez konieczności sprawdzania zachowania każdego kompilatora w tym przypadku jest użycie zarówno pragmy raz, jak i strażników.

#ifndef NR_TEST_H
#define NR_TEST_H
#pragma once

#include "Thing.h"

namespace MyApp
{
 // ...
}

#endif

W ten sposób uzyskujesz to, co najlepsze (między platformami i szybkość kompilacji pomocy).

Ponieważ pisanie jest dłuższe, osobiście używam narzędzia, które pomaga wygenerować to wszystko w bardzo złośliwy sposób (Visual Assist X).

Klaim
źródło
Czy Visual Studio nie optymalizuje #include strażników w obecnej postaci? Robią to inne (lepsze?) Kompilatory i wydaje mi się, że jest to dość łatwe.
Tom
1
Dlaczego stawiasz pragmapo ifndef? Czy jest jakaś korzyść?
user1095108
1
@ user1095108 Niektóre kompilatory używają osłon nagłówka jako separatora, aby wiedzieć, czy plik zawiera tylko kod, który należy utworzyć raz. Jeśli jakiś kod znajduje się poza osłonami nagłówka, cały plik może być uważany za możliwy do utworzenia więcej niż jeden raz. Jeśli ten sam kompilator nie obsługuje raz pragmy, wówczas zignoruje tę instrukcję. Dlatego umieszczenie pragmy w osłonach nagłówków jest najbardziej ogólnym sposobem upewnienia się, że przynajmniej osłony nagłówków można „zoptymalizować”.
Klaim
4

Nie zawsze.

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52566 ma ładny przykład dwóch plików, które mają być uwzględnione, ale błędnie uważane za identyczne z powodu identycznych znaczników czasu i treści (nie identyczna nazwa pliku) .

Omer
źródło
10
Byłby to błąd w kompilatorze. (próba skorzystania ze skrótu, którego nie powinna przyjąć).
rxantos
4
#pragma oncejest niestandardowy, więc cokolwiek kompilator zdecyduje się zrobić, jest „poprawne”. Oczywiście wtedy możemy zacząć mówić o tym, co jest „oczekiwane”, a co „przydatne”.
user7610
2

Używając gcc 3.4 i 4.1 na bardzo dużych drzewach (czasem korzystających z distcc ), nie widziałem jeszcze żadnego przyspieszenia, gdy używam #pragma zamiast zamiast lub w połączeniu ze standardowymi osłonami.

Naprawdę nie rozumiem, jak warto potencjalnie zagmatwać starsze wersje gcc lub nawet innych kompilatorów, ponieważ nie ma prawdziwych oszczędności. Nie wypróbowałem wszystkich różnych de-linterów, ale jestem gotów się założyć, że wielu z nich się zdezorientuje.

Ja również żałuję, że nie został przyjęty wcześniej, ale widzę argument: „Dlaczego potrzebujemy tego, skoro ifndef działa idealnie?”. Biorąc pod uwagę wiele mrocznych zakątków i złożoności C, osłony to jedna z najłatwiejszych, samo wyjaśniających się rzeczy. Jeśli masz choć niewielką wiedzę na temat działania preprocesora, powinny one być zrozumiałe.

Jeśli zauważysz znaczne przyspieszenie, zaktualizuj swoje pytanie.

Tim Post
źródło
2

Dzisiaj oldschoolowi strażnicy są tak szybcy, jak kiedyś #pragma. Nawet jeśli kompilator nie traktuje ich specjalnie, zatrzyma się, gdy zobaczy #ifndef WHATEVER i WHATEVER jest zdefiniowane. Otwarcie pliku jest dziś tandetne. Nawet gdyby nastąpiła poprawa, byłaby to rzędu milisekund.

Po prostu nie używam raz #pragmy, ponieważ nie przynosi to żadnych korzyści. Aby uniknąć kolizji z innymi strażnikami, używam czegoś takiego: CI_APP_MODULE_FILE_H -> CI = firmowe inicjały; APP = nazwa aplikacji; reszta jest oczywista.

CMircea
źródło
19
Czy to nie korzyść, że jest o wiele mniej pisania?
Andrey
1
Pamiętaj jednak, że kilka milisekund sto tysięcy razy to kilka minut. Duże projekty składają się z dziesięciu tysięcy plików, w tym dziesiątek nagłówków. Biorąc pod uwagę współczesne procesory wielordzeniowe, wejście / wyjście, w szczególności otwieranie wielu małych plików , jest jednym z głównych wąskich gardeł.
Damon
1
„Dzisiaj oldschoolowi strażnicy są tak szybcy jak #pragma raz”. Dzisiaj, a także wiele lat temu. Najstarsze dokumenty na stronie GCC dotyczą wersji 2.95 z 2001 roku, a optymalizacja między innymi strażników nie była wtedy nowością. To nie jest ostatnia optymalizacja.
Jonathan Wakely
4
Główną zaletą jest to, że osłony obejmują podatne na błędy i pracochłonne. Zbyt łatwo jest mieć dwa różne pliki o identycznych nazwach w różnych katalogach (a osłony dołączające mogą być tym samym symbolem) lub popełniać błędy kopiowania i wklejania podczas kopiowania osłon zabezpieczających. Raz Pragma jest mniej podatna na błędy i działa na wszystkich głównych platformach PC. Jeśli możesz go użyć, to jest lepszy styl.
AHelps
2

Główną różnicą jest to, że kompilator musiał otworzyć plik nagłówka, aby odczytać osłonę dołączania. Dla porównania, pragma raz powoduje, że kompilator śledzi plik i nie wykonuje żadnych operacji we / wy pliku, jeśli natrafi na inny plik dla tego samego pliku. Choć może to zabrzmieć bez znaczenia, może łatwo skalować się przy dużych projektach, szczególnie tych bez dobrego nagłówka, obejmujących dyscypliny.

To powiedziawszy, obecnie kompilatory (w tym GCC) są wystarczająco inteligentne, aby leczyć takich strażników jak pragma. tzn. nie otwierają pliku i unikają kary IO pliku.

W kompilatorach, które nie obsługują pragmy, widziałem ręczne implementacje, które są trochę uciążliwe ..

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

Osobiście podoba mi się #pragma raz, ponieważ pozwala uniknąć kłopotów z nazewnictwem i potencjalnych błędów literowych. Dla porównania jest to również bardziej elegancki kod. To powiedziawszy, dla przenośnego kodu nie powinno zaszkodzić mieć oba, chyba że kompilator narzeka na to.

Shammi
źródło
1
„To powiedziawszy, dzisiejsze kompilatory (w tym GCC) są wystarczająco inteligentne, aby leczyć strażników jak pragma.” Robili to od dziesięcioleci, może dłużej niż #pragma oncekiedykolwiek!
Jonathan Wakely,
Myślisz, że mnie źle zrozumiałeś. Chciałem powiedzieć raz przed pragmą, że wszystkie kompilatory poniosą wiele penitetów we / wy dla tego samego pliku h zawartego wiele razy na etapie preprocesora. Nowoczesne implementacje wykorzystują lepsze buforowanie plików na etapie preprocesora. Niezależnie od tego, bez pragm, etap preprocesora kończy się na tym, że obejmuje wszystko poza strażnikami. Z pragma raz cały plik jest pomijany. Z tego punktu widzenia pragma jest nadal korzystna.
Shammi
1
Nie, to źle, przyzwoite kompilatory pomijają cały plik nawet bez #pragma raz, nie otwierają pliku po raz drugi i nawet na niego nie patrzą, patrz gcc.gnu.org/onlinedocs/ cpp / Once-Only-Headers.html (nie ma to nic wspólnego z buforowaniem).
Jonathan Wakely
1
Z twojego łącza wydaje się, że optymalizacja odbywa się tylko w cpp. Niezależnie od tego w grę wchodzi buforowanie. Skąd preprocesor wie, aby uwzględnić kod poza strażnikami. Przykład ... extern int foo; #ifndef INC_GUARD # zdefiniować klasę INC_GUARD ClassInHeader {}; #endif W tym przypadku preprocesor będzie musiał dołączyć extern int foo; wiele razy, jeśli dołączasz ten sam plik wiele razy. Koniec dnia, nie ma sensu się o to kłócić, dopóki rozumiemy różnicę między #pragma raz i uwzględniamy strażników i jak różne kompilatory zachowują się z nimi oboje :)
Shammi
1
Oczywiście nie stosuje w tym optymalizacji.
Jonathan Wakely
1

Jeśli używamy msvc lub Qt (do Qt 4.5), ponieważ GCC (do 3.4), msvc oba obsługują #pragma once, nie widzę powodu, aby nie używać#pragma once .

Nazwa pliku źródłowego zwykle jest taka sama jak nazwa klasy i wiemy, że czasami potrzebujemy refaktora , aby zmienić nazwę nazwy klasy, wtedy musieliśmy zmienić #include XXXXrównież, więc myślę, że ręczne utrzymanie #include xxxxxnie jest mądrą pracą. nawet z rozszerzeniem Visual Assist X utrzymanie „xxxx” nie jest konieczną pracą.

raidsan
źródło
1

Dodatkowa uwaga dla osób myślących, że automatyczne jednorazowe dołączanie plików nagłówkowych jest zawsze pożądane: Generatory kodu buduję przy użyciu podwójnego lub wielokrotnego włączania plików nagłówkowych od dziesięcioleci. Zwłaszcza w przypadku generowania kodów pośredniczących biblioteki protokołów uważam, że bardzo wygodnie jest mieć wyjątkowo przenośny i wydajny generator kodu bez dodatkowych narzędzi i języków. Nie jestem jedynym programistą używającym tego schematu, jak pokazują blogi X-Macros . Nie byłoby to możliwe bez brakującego automatycznego zabezpieczenia.

Marcel
źródło
Czy szablony C ++ mogłyby rozwiązać problem? Rzadko znajduję jakąkolwiek uzasadnioną potrzebę makr z powodu tego, jak szablony C ++.
Jaśniejsze
1
Nasze wieloletnie doświadczenie zawodowe polega na tym, że używanie dojrzałego języka, infrastruktury oprogramowania i narzędzi przez cały czas daje nam, jako usługodawcom (systemy wbudowane), ogromną przewagę pod względem wydajności i elastyczności. Zamiast tego konkurenci opracowujący oprogramowanie i stosy systemów wbudowanych w języku C ++ mogą być bardziej zadowoleni z pracy. Ale zwykle wielokrotnie przewyższamy ich czasem wprowadzania na rynek, funkcjonalnością i elastycznością. Nether nie docenia wzrostu wydajności wynikającego z używania tego samego narzędzia w kółko. Zamiast tego Web-Devs cierpią z powodu wielu frameworków.
Marcel
Uwaga: nie obejmuje guards / # pragma raz w każdym pliku nagłówkowym wbrew samej zasadzie DRY. Widzę twój punkt widzenia w tej X-Macrofunkcji, ale nie jest to główne zastosowanie włączania, czy nie powinno być odwrotnie, jak nagłówek unguard / # pragma multi, gdybyśmy mieli trzymać się DRY?
caiohamamura
DRY oznacza „Dont repeat YOURSELF”. Chodzi o człowieka. To, co robi maszyna, nie ma nic wspólnego z tym paradygmatem. Szablony C ++ często się powtarzają, kompilatory C również to robią (np. Rozwijanie pętli) i każdy komputer powtarza prawie wszystko niewiarygodne często i szybko.
Marcel