Het gebruik van dubbele bewakers in C++

Dus ik had onlangs een discussie waar ik werk, waarin ik het gebruik van een dubbeleinclude bewaker boven een enkele bewaker in twijfel trok. Wat ik bedoel met dubbele afschermingis als volgt:

Headerbestand, “header_a.hpp”:

#ifndef __HEADER_A_HPP__
#define __HEADER_A_HPP__
...
...
#endif

Als u het headerbestand ergens opneemt, in een header- of bronbestand:

#ifndef __HEADER_A_HPP__
#include "header_a.hpp"
#endif

Nu begrijp ik dat het gebruik van de guard in header-bestanden is om meerdere opname van een reeds gedefinieerd header-bestand te voorkomen, het is gebruikelijk en goed gedocumenteerd. Als de macro al is gedefinieerd, wordt het hele headerbestand door de compiler als ‘leeg’ gezien en wordt dubbele opname voorkomen. Eenvoudig genoeg.

Het probleem dat ik niet begrijp is het gebruik van #ifndef __HEADER_A_HPP__en #endifrond de #include "header_a.hpp". De collega heeft mij verteld dat dit een tweede beschermingslaag toevoegt aan insluitsels, maar ik zie niet in hoe die tweede laag zelfs maar nuttig is als de eerste laag absoluut zijn werk doet (of niet?).

Het enige voordeel dat ik kan bedenken, is dat het regelrecht voorkomt dat de linker de moeite neemt om het bestand te vinden. Is dit bedoeld om de compilatietijd te verbeteren (wat niet als een voordeel werd genoemd), of is er hier iets anders aan het werk dat ik niet zie?


Antwoord 1, autoriteit 100%

Ik ben er vrij zeker van dat het een slechte gewoonte is om nog een include guard toe te voegen, zoals:

#ifndef __HEADER_A_HPP__
#include "header_a.hpp"
#endif

Hier zijn enkele redenen waarom:

  1. Om dubbele opname te voorkomen is het voldoende om een ​​gebruikelijke include guard toe te voegen in het header-bestand zelf. Het doet het werk goed. Een andere bewaking op de plaats van opname verpest de code en vermindert de leesbaarheid.

  2. Het voegt onnodige afhankelijkheden toe. Als u de include guard in het header-bestand wijzigt, moet u deze op alleplaatsen wijzigen waar de header is opgenomen.

  3. Het is zeker niet de duurste operatie om het hele compilatie-/koppelingsproces te vergelijken, dus het kan de totale bouwtijd nauwelijks verkorten.

  4. Elke compiler die iets waard is optimiseert al bestandsbrede include-guards.


Antwoord 2, autoriteit 48%

De reden voor het plaatsen van include guards in het headerbestand is om te voorkomen dat de inhoud van de header meer dan eens in een vertaaleenheid wordt getrokken. Dat is een normale, al lang bestaande praktijk.

De reden voor het plaatsen van redundante include-guards in een bron-bestand is om te voorkomen dat je het header-bestand moet openen dat wordt meegeleverd, en vroeger kon dat de compilatie aanzienlijk versnellen. Tegenwoordig gaat het openen van een bestand veel sneller dan vroeger; verder zijn compilers behoorlijk slim in het onthouden welke bestanden ze al hebben gezien, en ze begrijpen het idioom van de include guard, dus kunnen ze er zelf achter komen dat ze het bestand niet opnieuw hoeven te openen. Dat is even zwaaien met de hand, maar het komt erop neer dat deze extra laag niet meer nodig is.

EDIT: een andere factor hier is dat het compileren van C++ veelingewikkelder is dan het compileren van C, dus het duurt veellanger, waardoor de tijd die wordt besteed aan het openen van onder meer bestanden een kleinere, minder belangrijk deel van de tijd die nodig is om een ​​vertaaleenheid samen te stellen.


Antwoord 3, autoriteit 21%

Het enige voordeel dat ik kan bedenken, is dat het regelrecht voorkomt dat de linker de moeite neemt om het bestand te vinden.

De linker wordt op geen enkele manier beïnvloed.

Het kan voorkomen dat de pre-processorde moeite neemt om het bestand te vinden, maar als de bewaker is gedefinieerd, betekent dit dat het het bestand al heeft gevonden. Ik vermoed dat als de voorprocestijd al wordt verkort, het effect vrij minimaal zou zijn, behalve in de meest pathologisch recursief opgenomen wangedrocht.

Het heeft een nadeel dat als de bewaker ooit wordt gewijzigd (bijvoorbeeld vanwege een conflict met een andere bewaker), alle voorwaarden vóór de include-richtlijnen moeten worden gewijzigd om te kunnen werken. En als iets anders de vorige bewaker gebruikt, dan moeten de conditionals worden gewijzigd om de include-richtlijn zelf correct te laten werken.

P.S. __HEADER_A_HPP__is een symbool dat is gereserveerd voor de implementatie, dus het is niet iets dat u mag definiëren. Gebruik een andere naam voor de bewaker.


Antwoord 4, autoriteit 16%

Oudere compilers op meer traditionele (mainframe) platforms (we hebben het hier over het midden van de jaren 2000) hadden vroeger niet de optimalisatie die in andere antwoorden wordt beschreven, en dus werd het echt gebruikt om de voorverwerkingstijd aanzienlijk te vertragen door opnieuw te moeten lees header-bestanden die al zijn opgenomen (rekening houdend met het feit dat u in een groot, monolithisch, enterprise-y-project VEEL header-bestanden zult opnemen). Als voorbeeld heb ik gegevens gezien die een 26-voudige versnelling aangeven voor een bestand met 256 headerbestanden die elk dezelfde 256 headerbestanden bevatten op de VisualAge C++ 6 voor AIX-compiler (die dateert uit het midden van de jaren 2000). Dit is een nogal extreem voorbeeld, maar dit soort versnelling klopt wel.

Echter, alle moderne compilers, zelfs op mainframeplatforms zoals AIX en Solaris, voeren genoeg optimalisatie uit voor het opnemen van headers, zodat het verschil tegenwoordig echt te verwaarlozen is. Daarom is er geen goede reden meer om deze te hebben.

Dit verklaart echter waarom sommige bedrijven nog steeds vasthouden aan de praktijk, omdat het relatief recent (tenminste in termen van de C/C++-codebase) nog de moeite waard was voor zeer grote monolithische projecten.


Antwoord 5, autoriteit 7%

Hoewel er mensen zijn die ertegen zijn, werkt ‘#pragma once’ in de praktijk perfect en ondersteunen de hoofdcompilers (gcc/g++, vc++) het.

Dus welke puristische argumentatie mensen ook verspreiden, het werkt een stuk beter:

  1. Snel
  2. Geen onderhoud, geen problemen met mysterieuze niet-opname omdat je een oude vlag hebt gekopieerd
  3. Enkele regel met duidelijke betekenis versus cryptische regels verspreid in bestand

Dus simpel gezegd:

#pragma once

aan het begin van het bestand, en dat is alles. Geoptimaliseerd, onderhoudbaar en klaar voor gebruik.

Other episodes