Meerdere klassen in een header-bestand versus een enkel header-bestand per klasse

Om welke reden dan ook heeft ons bedrijf een codeerrichtlijn waarin staat:

Each class shall have it's own header and implementation file.

Dus als we een klasse zouden schrijven met de naam MyString, zouden we een bijbehorende MyStringh.hen MyString.cxxnodig hebben.

Doet iemand anders dit? Heeft iemand het resultaat van het compileren van prestaties gezien? Compileren 5000 klassen in 10000 bestanden net zo snel als 5000 klassen in 2500 bestanden? Zo niet, is het verschil merkbaar?

[We coderen C++ en gebruiken GCC 3.4.4 als onze dagelijkse compiler]


Antwoord 1, autoriteit 100%

De term hier is vertaaleenheiden u wilt echt (indien mogelijk) één klasse per vertaaleenheid hebben, dwz één klasse-implementatie per .cpp-bestand, met een bijbehorend .h-bestand van hetzelfde naam.

Het is meestal efficiënter (vanuit het oogpunt van compileren/linken) om dingen op deze manier te doen, vooral als je dingen doet als incrementele link enzovoort. Het idee is dat vertaaleenheden zodanig geïsoleerd zijn dat, wanneer een vertaaleenheid verandert, u niet veel dingen opnieuw hoeft te bouwen, zoals u zou moeten doen als u veel abstracties in één enkele vertaaleenheid zou gaan samenvoegen.

Ook zult u merken dat veel fouten/diagnoses worden gemeld via de bestandsnaam (“Fout in Myclass.cpp, regel 22”) en het helpt als er een één-op-één correspondentie is tussen bestanden en klassen. (Of je zou het een 2-op-1 correspondentie kunnen noemen).


Antwoord 2, autoriteit 87%

Overweldigd door duizenden regels code?

Het kan overdreven lijken om één set header-/bronbestanden per klasse in een directory te hebben. En als het aantal lessen richting 100 of 1000 gaat, kan het zelfs beangstigend zijn.

Maar na te hebben gespeeld met bronnen volgens de filosofie “laten we alles in elkaar zetten”, is de conclusie dat alleen degene die het bestand heeft geschreven enige hoop heeft om niet verloren te gaan. Zelfs met een IDE is het gemakkelijk om dingen over het hoofd te zien, want wanneer je speelt met een bron van 20.000 regels, sluit je je geest voor alles wat niet echt betrekking heeft op je probleem.

Voorbeeld uit de praktijk: de klassenhiërarchie gedefinieerd in die bronnen met duizend regels sloot zichzelf op in een diamant-overerving, en sommige methoden werden in onderliggende klassen overschreven door methoden met exact dezelfde code. Dit werd gemakkelijk over het hoofd gezien (wie wil een broncode van 20.000 regels onderzoeken/controleren?), en toen de oorspronkelijke methode werd gewijzigd (bugcorrectie), was het effect niet zo universeel als was toegestaan.

Afhankelijkheden die circulair worden?

Ik had dit probleem met sjablooncode, maar ik zag soortgelijke problemen met gewone C++- en C-code.

Door uw bronnen op te splitsen in 1 koptekst per struct/klasse kunt u:

  • Versnel het compileren omdat u voorwaartse declaratie van symbolen kunt gebruiken in plaats van hele objecten op te nemen
  • Circulaire afhankelijkheden hebben tussen klassen (§) (d.w.z. klasse A heeft een pointer naar B en B heeft een pointer naar A)

In brongestuurde code kunnen klassenafhankelijkheden ertoe leiden dat klassen regelmatig op en neer in het bestand worden verplaatst, alleen maar om de header te compileren. Je wilt de evolutie van dergelijke zetten niet bestuderen als je hetzelfde bestand in verschillende versies vergelijkt.

Het hebben van aparte headers maakt de code modulairder, sneller te compileren en maakt het gemakkelijker om de evolutie ervan te bestuderen door middel van verschillende versies diffs

Voor mijn sjabloonprogramma moest ik mijn headers in twee bestanden verdelen: het .HPP-bestand met de declaratie/definitie van de sjabloonklasse en het .INL-bestand dat de definities van de genoemde klassenmethoden bevat.

Als u al deze code in één en slechts één unieke header plaatst, betekent dit dat u klassedefinities aan het begin van dit bestand plaatst en de methodedefinities aan het einde.

En dan, als iemand maar een klein deel van de code nodig had, met de one-header-only oplossing, zouden ze nog steeds moeten betalen voor de langzamere compilatie.

(§) Merk op dat je circulaire afhankelijkheden tussen klassen kunt hebben als je weet welke klasse welke bezit. Dit is een discussie over klassen die kennis hebben van het bestaan ​​van andere klassen, niet shared_ptr circulaire afhankelijkheden antipatroon.

Nog een laatste woord: headers moeten zelfvoorzienend zijn

Eén ding moet echter worden gerespecteerd door een oplossing van meerdere headers en meerdere bronnen.

Als je één header opneemt, ongeacht welke header, moet je bron netjes worden gecompileerd.

Elke kop moet zelfvoorzienend zijn. Het is de bedoeling dat je code ontwikkelt, niet het zoeken naar schatten door je 10.000+ bronbestandenproject te doorlopen om te ontdekken welke kop het symbool definieert in de kop van 1000 regels die je moet opnemen vanwege éénopsomming.

Dit betekent dat elke header alle gebruikte symbolen definieert of doorstuurt, of alle benodigde headers bevat (en alleen de benodigde headers).

Vraag over circulaire afhankelijkheden

underscore-dvraagt:

Kun je uitleggen hoe het gebruik van aparte headers enig verschil maakt voor circulaire afhankelijkheden? Ik denk van niet. We kunnen triviaal een circulaire afhankelijkheid creëren, zelfs als beide klassen volledig in dezelfde kop zijn gedeclareerd, simpelweg door de ene van tevoren te declareren voordat we er een handvat voor declareren in de andere. Al het andere lijkt geweldige punten, maar het idee dat afzonderlijke headers circulaire afhankelijkheden mogelijk maken, lijkt ver weg

underscore_d, 13 nov om 23:20

Stel dat je 2 lessjablonen hebt, A en B.

Stel dat de definitie van klasse A (resp. B) een verwijzing naar B (resp. A) heeft. Laten we ook zeggen dat de methoden van klasse A (resp. B) daadwerkelijk methoden aanroepen van B (resp. A).

Je hebt een circulaire afhankelijkheid, zowel in de definitie van de klassen als in de implementaties van hun methoden.

Als A en B normale klassen waren, en de methoden van A en B in .CPP-bestanden zaten, zou er geen probleem zijn: u zou een forward-declaratie gebruiken, een header voor elke klassedefinitie hebben, dan zou elke CPP zowel HPP bevatten .

Maar aangezien je sjablonen hebt, moet je die patronen hierboven reproduceren, maar alleen met koppen.

Dit betekent:

  1. een definitiekop A.def.hpp en B.def.hpp
  2. een implementatieheader A.inl.hpp en B.inl.hpp
  3. voor het gemak een “naïeve” kop A.hpp en B.hpp

Elke kop heeft de volgende kenmerken:

  1. In A.def.hpp (resp. B.def.hpp) heb je een voorwaartse declaratie van klasse B (resp. A), waarmee je een pointer/verwijzing naar die klasse kunt declareren
  2. A.inl.hpp (resp. B.inl.hpp) zal zowel A.def.hpp als B.def.hpp bevatten, waardoor methoden van A (resp. B) de klasse B (resp. A).
  3. A.hpp (resp. B.hpp) omvat rechtstreeks zowel A.def.hpp als A.inl.hpp (resp. B.def.hpp en B.inl.hpp)
  4. Natuurlijk moeten alle headers zelfvoorzienend zijn en worden beschermd door header-guards

De naïeve gebruiker zal A.hpp en/of B.hpp opnemen, en dus de hele puinhoop negeren.

En met die organisatie kan de bibliotheekschrijver de cirkelvormige afhankelijkheden tussen A en B oplossen, terwijl beide klassen in afzonderlijke bestanden worden bewaard, gemakkelijk te navigeren als je het schema eenmaal begrijpt.

Houd er rekening mee dat het een edge-case was (twee sjablonen die elkaar kenden). Ik verwacht dat de meeste code nietdie truc nodig heeft.


Antwoord 3, autoriteit 14%

We doen dat op het werk, het is gewoon makkelijker om dingen te vinden als de klasse en bestanden dezelfde naam hebben. Wat de prestaties betreft, zou je echt geen 5000 klassen in een enkel project moeten hebben. Als je dat doet, is misschien wat refactoring op zijn plaats.

Dat gezegd hebbende, er zijn gevallen waarin we meerdere klassen in één bestand hebben. En dan is het gewoon een privé-helperklasse voor de hoofdklasse van het bestand.


Antwoord 4, autoriteit 12%

+1 voor scheiding. Ik kwam net op een project waar sommige klassen in bestanden met een andere naam staan, of op één hoop worden gegooid met een andere klasse, en het is onmogelijk om deze op een snelle en efficiënte manier te vinden. Je kunt meer bronnen gebruiken voor een build – je kunt de verloren programmeertijd niet inhalen omdat hij het juiste bestand niet kan vinden om te bewerken.


Antwoord 5, autoriteit 10%

Behalve dat het “duidelijker” is, maakt het scheiden van klassen in afzonderlijke bestanden het voor meerdere ontwikkelaars gemakkelijker om elkaar niet op de hielen te zitten. Er zal minder samensmelting zijn als het tijd is om wijzigingen door te voeren in uw versiebeheertool.


Antwoord 6, autoriteit 7%

De meeste plaatsen waar ik heb gewerkt, hebben deze praktijk gevolgd. Ik heb eigenlijk codeerstandaarden geschreven voor BAE (Aust.) samen met de redenen waarom in plaats van iets in steen te hakken zonder echte rechtvaardiging.

Wat betreft uw vraag over bronbestanden, het is niet zozeer tijd om te compileren, maar meer een kwestie van het kunnen vinden van het relevante codefragment. Niet iedereen gebruikt een IDE. En wetende dat u gewoon naar MyClass.h en MyClass.cpp zoekt, bespaart u echt tijd in vergelijking met het uitvoeren van “grep MyClass *.(h|cpp)” over een aantal bestanden en vervolgens het filteren van de #include MyClass.h-instructies…

Let wel, er zijn oplossingen voor de impact van grote aantallen bronbestanden op de compileertijden. Zie Large Scale C++ Software Design door John Lakos voor een interessante discussie.

Misschien wil je ook Code Complete van Steve McConnell lezen voor een uitstekend hoofdstuk over coderingsrichtlijnen. Eigenlijk is dit boek geweldig om te lezen en ik kom er regelmatig op terug


Antwoord 7, autoriteit 3%

Het is gebruikelijk om dit te doen, vooral om .h op te nemen in de bestanden die het nodig hebben. Natuurlijk worden de prestaties beïnvloed, maar probeer niet aan dit probleem te denken totdat het zich voordoet :).
Het is beter om te beginnen met de bestanden gescheiden en daarna de .h’s die vaak worden gebruikt samen te voegen om de prestaties te verbeteren als dat echt nodig is. Het komt allemaal neer op afhankelijkheden tussen bestanden en dit is heel specifiek voor elk project.


Antwoord 8, autoriteit 3%

De beste praktijk, zoals anderen al hebben gezegd, is om elke klasse in zijn eigen vertaaleenheid te plaatsen vanuit het oogpunt van onderhoud van de code en begrijpelijkheid. Op grootschalige systemen is dit echter soms niet aan te raden – zie de sectie getiteld “Maak die bronbestanden groter” in dit artikeldoor Bruce Dawson voor een bespreking van de afwegingen.


Antwoord 9, autoriteit 2%

Ik vond deze richtlijnen vooral handig als het gaat om header-bestanden:
http://google-styleguide.googlecode.com/svn/trunk/cppguide .xml#Header_Files


Antwoord 10

Het is erg handig om slechts één klasse per bestand te hebben, maar als je bouwt via bulkbuild-bestanden die alle individuele C++-bestanden bevatten, zorgt dit voor snellere compilaties omdat de opstarttijd voor veel compilers relatief groot is.

p>


Antwoord 11

Het verbaast me dat bijna iedereen voorstander is van één bestand per klas. Het probleem daarmee is dat het in het tijdperk van ‘refactoring’ moeilijk kan zijn om de bestands- en klassenamen synchroon te houden. Elke keer dat je een klassenaam wijzigt, moet je ook de bestandsnaam wijzigen, wat betekent dat je ook overal een wijziging moet aanbrengen waar het bestand is opgenomen.

Persoonlijk groepeer ik gerelateerde klassen in een enkel bestand en geef zo’n bestand een betekenisvolle naam die niet hoeft te veranderen, zelfs als de naam van een klasse verandert. Het hebben van minder bestanden maakt het ook gemakkelijker om door een bestandsstructuur te bladeren.
Ik gebruik Visual Studio op Windows en Eclipse CDT op Linux, en beide hebben sneltoetsen die je rechtstreeks naar een klassendeclaratie brengen, dus het vinden van een klassendeclaratie is gemakkelijk en snel.

Dat gezegd hebbende, denk ik, nadat een project is voltooid, of de structuur ervan heeft ‘gestold’, en veranderingen worden zeldzaam, het kan logisch zijn om één klas per bestand te hebben. Ik wou dat er een tool was die de lessen zou kunnen extraheren en ze in verschillende .h- en .cpp-bestanden kunnen plaatsen. Maar ik zie dit niet als essentieel.

De keuze is ook afhankelijk van het type project dat men werkt. Naar mijn mening verdient het probleem niet een zwart-wit antwoord, aangezien beide keuze voor- en nadelen heeft.

Other episodes