So hatte ich kürzlich eine Diskussion, in der ich arbeite, in der ich die Verwendung einer doppelten Einschlusswache über eine einzelne Wache in Frage stellte . Was ich mit Doppelwache meine , ist wie folgt:
Header-Datei "header_a.hpp":
#ifndef __HEADER_A_HPP__
#define __HEADER_A_HPP__
...
...
#endif
Wenn Sie die Header-Datei an einer beliebigen Stelle einfügen, entweder in eine Header- oder eine Quelldatei:
#ifndef __HEADER_A_HPP__
#include "header_a.hpp"
#endif
Jetzt verstehe ich, dass die Verwendung des Schutzes in Header-Dateien dazu dient, die mehrfache Aufnahme einer bereits definierten Header-Datei zu verhindern. Dies ist üblich und gut dokumentiert. Wenn das Makro bereits definiert ist, wird die gesamte Header-Datei vom Compiler als "leer" angesehen und die doppelte Einbeziehung wird verhindert. Einfach genug.
Das Problem, das ich nicht verstehe, ist die Verwendung von #ifndef __HEADER_A_HPP__
und #endif
um das #include "header_a.hpp"
. Der Mitarbeiter hat mir gesagt, dass dies den Einschlüssen eine zweite Schutzschicht hinzufügt, aber ich kann nicht erkennen, wie nützlich diese zweite Schicht überhaupt ist, wenn die erste Schicht den Job absolut erledigt (oder?).
Der einzige Vorteil, den ich mir einfallen lassen kann, ist, dass der Linker sich nicht mehr darum kümmert, die Datei zu finden. Soll dies die Kompilierungszeit verbessern (was nicht als Vorteil erwähnt wurde), oder ist hier noch etwas anderes am Werk, das ich nicht sehe?
__HEADER_A_HPP__
) enthalten, und Namen, die mit einem Unterstrich gefolgt von einem Großbuchstaben beginnen, für die Implementierung reserviert sind. Verwenden Sie sie nicht in Ihrem Code.Antworten:
Ich bin mir ziemlich sicher, dass es eine schlechte Praxis ist, einen weiteren Include-Guard hinzuzufügen, wie:
#ifndef __HEADER_A_HPP__ #include "header_a.hpp" #endif
Hier sind einige Gründe warum:
Um eine doppelte Aufnahme zu vermeiden, reicht es aus, einen üblichen Include-Schutz in die Header-Datei selbst einzufügen. Es macht den Job gut. Ein weiterer Include-Schutz anstelle des Einschlusses bringt den Code nur durcheinander und verringert die Lesbarkeit.
Es werden unnötige Abhängigkeiten hinzugefügt. Wenn Sie den Include Guard in der Header-Datei ändern, müssen Sie ihn an allen Stellen ändern, an denen der Header enthalten ist.
Es ist definitiv nicht die teuerste Operation, die den gesamten Kompilierungs- / Verknüpfungsprozess vergleicht, sodass die gesamte Erstellungszeit kaum verkürzt werden kann.
Jeder Compiler, der etwas wert ist, optimiert bereits dateiweite Include-Guards .
quelle
If you change include guard inside the header file you have to change it in all places where the header is included.
.... nun, technisch gesehen, nein, das tust du nicht, aber ich denke, das beweist den Punkt noch weiter.Der Grund für das Einfügen von Include-Schutzvorrichtungen in die Header- Datei besteht darin, zu verhindern, dass der Inhalt des Headers mehrmals in eine Übersetzungseinheit gezogen wird. Das ist eine normale, seit langem etablierte Praxis.
Der Grund für das Einfügen redundanter Include-Guards in eine Quelldatei besteht darin, dass die enthaltene Header-Datei nicht geöffnet werden muss, und dies in früheren Zeiten, die die Kompilierung erheblich beschleunigen könnten. Heutzutage ist das Öffnen einer Datei viel schneller als früher. Darüber hinaus sind Compiler ziemlich schlau darin, sich zu merken, welche Dateien sie bereits gesehen haben, und sie verstehen die Include-Guard-Sprache, sodass sie selbst herausfinden können, dass sie die Datei nicht erneut öffnen müssen. Das ist ein bisschen Handbewegung, aber unter dem Strich wird diese zusätzliche Ebene nicht mehr benötigt.
BEARBEITEN: Ein weiterer Faktor ist, dass das Kompilieren von C ++ weitaus komplizierter ist als das Kompilieren von C. Daher dauert es viel länger, sodass der Zeitaufwand für das Öffnen von Include-Dateien einen geringeren, weniger bedeutenden Teil der Zeit zum Kompilieren einer Übersetzungseinheit ausmacht.
quelle
#if
-#endif
Paar, aber Leerzeichen und Kommentare erlaubt sind.“ Beinhaltet "Token"#pragma once
?#if
-#endif
Paar , ohne die Optimierung zu deaktivieren sind Leerzeichen und Kommentare.“ Aber du solltest es#pragma once
sowieso nicht benutzen .#pragma once
in den#ifndef/#endif
Block setzen. Wir verwenden es jedoch nicht,#pragma once
da einer der Compiler, die wir bei der Arbeit verwenden, es nicht unterstützt.#pragma once
ist kein Token, sondern eine Präprozessor-Direktive. Diese sind jedoch auch nicht zulässig, damit die Optimierung funktioniert. Allerdings unterstützt GCC#pragma once
daher die Optimierung und#pragma once
ist redundant. Wie Tom Tanner vorgeschlagen hat, können Sie das Pragma genauso gut in den Block legen , wenn Sie beide verwenden#pragma once
und Wachen einschließen#ifndef/#endif
. In dem unwahrscheinlichen Fall, dass Ihr Compiler über die Optimierung mehrerer Includes verfügt#pragma once
, diese jedoch nicht unterstützt , sollten Sie dies abdecken. Das#include
Verhalten ist ohnehin notorisch implementierungsabhängig.Der Linker ist in keiner Weise betroffen.
Dies könnte verhindern, dass sich der Vorprozessor die Mühe macht, die Datei zu finden. Wenn der Schutz jedoch definiert ist, bedeutet dies, dass er die Datei bereits gefunden hat. Ich vermute, dass, wenn die Vorverarbeitungszeit überhaupt verkürzt wird, der Effekt ziemlich gering wäre, außer in der pathologisch rekursiv eingeschlossenen Monstrosität.
Es hat den Nachteil, dass, wenn die Wache jemals geändert wird (z. B. aufgrund eines Konflikts mit einer anderen Wache), alle Bedingungen vor den Include-Anweisungen geändert werden müssen, damit sie funktionieren. Und wenn etwas anderes den vorherigen Schutz verwendet, müssen die Bedingungen geändert werden, damit die Include-Direktive selbst ordnungsgemäß funktioniert.
PS
__HEADER_A_HPP__
ist ein Symbol, das der Implementierung vorbehalten ist. Sie können es also nicht definieren. Verwenden Sie einen anderen Namen für die Wache.quelle
__HEADER_A_HPP__
ist der Implementierung vorbehalten. Was meinen Sie damit? Ist es speziell die Verwendung dieser Semantik, wiemath.hpp
und__MATH_HPP__
?__HEADER_A_HPP__
wann Sie sie einschließenheader_a.hpp
. Dies unterbricht natürlich Ihren Header Guard, der davon ausgeht, dass er nur in der zweiten Zeile definiert ist.Ältere Compiler auf traditionelleren (Mainframe-) Plattformen (wir sprechen hier von Mitte der 2000er Jahre) hatten nicht die in anderen Antworten beschriebene Optimierung und verlangsamten daher die Vorverarbeitungszeit beim erneuten Lesen von Header-Dateien erheblich Diese wurden bereits aufgenommen (unter Berücksichtigung, dass Sie in einem großen, monolithischen Unternehmensprojekt eine Menge Header-Dateien einschließen werden). Als Beispiel habe ich Daten gesehen, die eine 26-fache Beschleunigung für eine Datei mit jeweils 256 Header-Dateien anzeigen, einschließlich derselben 256 Header-Dateien auf dem VisualAge C ++ 6 für AIX-Compiler (der Mitte der 2000er Jahre stammt). Dies ist ein ziemlich extremes Beispiel, aber diese Art der Beschleunigung summiert sich.
Alle modernen Compiler, selbst auf Mainframe-Plattformen wie AIX und Solaris, führen jedoch eine ausreichende Optimierung für die Einbeziehung von Headern durch, sodass der Unterschied heutzutage wirklich vernachlässigbar ist. Daher gibt es keinen guten Grund mehr, diese zu haben.
Dies erklärt jedoch, warum einige Unternehmen immer noch an der Praxis festhalten, da sie sich vor relativ kurzer Zeit (zumindest in Bezug auf das Alter der C / C ++ - Codebasis) für sehr große monolithische Projekte noch gelohnt hat.
quelle
gfortran
erledigte den gleichen Job innerhalb eines Bruchteils dieser Zeit. Vielleicht sind IBM Compiler nicht die beste Referenz für die Messung der Kompilierungsgeschwindigkeit. Auf einem modernen Kernel befinden sich diese 256 Header-Dateien immer noch im Seiten-Cache, wenn der Compiler versucht, sie zu lesen. Daher sollte das Öffnen von 64 KB für dieselben 256 Dateien nicht länger als eine Sekunde dauern, wenn ein Systemaufruf weniger als 10 Mikrosekunden beträgt .Obwohl es Leute gibt, die dagegen argumentieren, funktioniert '#pragma einmal' in der Praxis perfekt und die Hauptcompiler (gcc / g ++, vc ++) unterstützen es.
Was auch immer puristische Argumentation verbreitet, es funktioniert viel besser:
Also einfach gesagt:
#pragma once
am Anfang der Datei, und das war's. Optimiert, wartbar und einsatzbereit.
quelle
#pragma once
(während ein Standard-Include-Guard in Ordnung wäre). Da alle Compiler entweder den Include Guard (echte Compiler) optimieren oder den Compile Guard (#pragma once
Toy Compiler) nicht kompilieren können , ist er auch nicht schneller als ein Standard Include Guard.#pragma once
wurde für die Standardisierung in Betracht gezogen, aber abgelehnt, weil es nicht zuverlässig umgesetzt werden kann