Best practices voor kopbestanden voor typedefs

Ik gebruik shared_ptr en STL veelvuldig in een project, en dit leidt tot te lange, foutgevoelige typen zoals shared_ptr< vector< shared_ptr<const Foo> > >(Ik ben bij voorkeur een ObjC-programmeur, waar lange namen de norm zijn, en toch is dit veel te veel.) Het zou veel duidelijker zijn, denk ik, om dit consequent FooListPtren documenteert de naamgevingsconventie dat “Ptr” shared_ptr betekent en “List” vector van shared_ptr betekent.

Dit is gemakkelijk te typen, maar het veroorzaakt hoofdpijn met de headers. Ik schijn verschillende opties te hebben om FooListPtrte definiëren:

  • Foo.h. Dat verstrengelt alle headers en zorgt voor serieuze bouwproblemen, dus het is een niet-starter.
  • FooFwd.h (“forward header”). Dit is wat Effective C++suggereert, gebaseerd op iosfwd.h. Het is erg consistent, maar de overhead van het onderhouden van tweemaal het aantal headers lijkt op zijn best vervelend.
  • Common.h (zet ze allemaal samen in één bestand). Dit doodt herbruikbaarheid door veel niet-gerelateerde typen te verstrengelen. U kunt nu niet zomaar een object oppakken en naar een ander project verplaatsen. Dat is een niet-starter.
  • Een of andere fancy #define-magie die typedef is, als het nog niet is getypt. Ik heb een blijvende hekel aan de preprocessor omdat ik denk dat het voor nieuwe mensen moeilijk is om de code te kraken, maar misschien….
  • Gebruik een vectorsubklasse in plaats van een typedef. Dit lijkt gevaarlijk…

Zijn hier praktische tips? Hoe komen ze uit in echte code, wanneer herbruikbaarheid, leesbaarheid en consistentie voorop staan?

Ik heb deze community-wiki gemarkeerd als anderen extra opties voor discussie willen toevoegen.


Antwoord 1, autoriteit 100%

Ik programmeer aan een project dat klinkt alsof het de common.hmethode gebruikt. Het werkt heel goed voor dat project.

Er is een bestand met de naam ForwardsDecl.hdat zich in de vooraf gecompileerde header bevindt en eenvoudig alle belangrijke klassen en benodigde typedefs doorstuurt. In dit geval wordt unique_ptrgebruikt in plaats van shared_ptr, maar het gebruik zou vergelijkbaar moeten zijn. Het ziet er zo uit:

// Forward declarations
class ObjectA;
class ObjectB;
class ObjectC;
// List typedefs
typedef std::vector<std::unique_ptr<ObjectA>> ObjectAList;
typedef std::vector<std::unique_ptr<ObjectB>> ObjectBList;
typedef std::vector<std::unique_ptr<ObjectC>> ObjectCList;

Deze code wordt geaccepteerd door Visual C++ 2010, ook al worden de klassen alleen voorwaarts gedeclareerd (de volledige klassedefinities zijn niet nodig, dus het is niet nodig om het headerbestand van elke klasse op te nemen). Ik weet niet of dat standaard is en dat andere compilers de volledige klassendefinitie nodig hebben, maar het is handig dat dit niet het geval is: een andere klasse (ObjectD) kan een ObjectAList als lid hebben, zonder dat ObjectA.h hoeft op te nemen – dit kan helpt echt de afhankelijkheden van koptekstbestanden te verminderen!

Onderhoud is niet echt een probleem, omdat de forwards-declaraties maar één keer hoeven te worden geschreven en alle daaropvolgende wijzigingen alleen hoeven te gebeuren in de volledige declaratie in het headerbestand van de klasse (en dit zal ertoe leiden dat minder bronbestanden opnieuw moeten worden gecompileerd vanwege verminderde afhankelijkheden).

Eindelijk lijkt het erop dat dit kan worden gedeeld tussen projecten (ik heb het zelf niet geprobeerd) want zelfs als een project niet echt een ObjectA declareert, maakt het niet uit, omdat het alleen forwards is gedeclareerd en als je het niet gebruikt het maakt de compiler niets uit. Daarom kan het bestand de namen van klassen bevatten van alle projecten waarin het wordt gebruikt, en het maakt niet uit of er enkele ontbreken voor een bepaald project. Het enige dat nodig is, is de noodzakelijke volledige declaratieheader (bijv. ObjectA.h) is opgenomen in alle bron(.cpp)-bestanden die eigenlijkgebruiken hen.


Antwoord 2, autoriteit 40%

Ik zou kiezen voor een gecombineerde aanpak van forward-headers en een soort common.h-header die specifiek is voor uw project en alleen alle forward-declaratieheaders en alle andere dingen die veel voorkomen en lichtgewicht.

Je klaagt over de overhead van het onderhouden van tweemaal het aantal headers, maar ik denk niet dat dit een te groot probleem zou moeten zijn: de forward headers hoeven meestal maar een zeer beperkt aantal typen te kennen (één?) en soms niet eens het volledige type.

Je zou zelfs kunnen proberen de headers automatisch te genereren met een script (dit wordt bijvoorbeeld gedaan in SeqAn) als er zijn echtzoveel kopteksten.


Antwoord 3, autoriteit 27%

+1 voor het documenteren van de typedef-conventies.

  • Foo.h– kun je de problemen beschrijven die je daarmee hebt?
  • FooFwd.h– Ik zou ze in het algemeen niet gebruiken, alleen op “voor de hand liggende hotspots”. (Ja, “hotspots” zijnmoeilijk te bepalen).
    Het verandert de regels IMO niet, want als je een fwd-header introduceert, gaan de bijbehorende typedefs van foo.h daarheen.
  • Common.h– cool voor kleine projecten, maar schaalt niet, daar ben ik het mee eens.
  • Een soort van fancy #define… ALSJEBLIEFT NEE!…
  • Gebruik een vectorsubklasse– maakt het niet beter.
    Je zou echter inperking kunnen gebruiken.

Dus hier de voorlopige suggesties (herzien van die andere vraag..)

  1. Standaard type headers <boost/shared_ptr.hpp>, <vector>etc. kunnen in een vooraf gecompileerd header/gedeeld include-bestand voor de projecteren. Dit is niet slecht.(Persoonlijk voeg ik ze nog steeds toe waar nodig, maar dat werkt naast ze in de PCH te plaatsen.)

  2. Als de container een implementatiedetail is, gaan de typedefs naar de plaats waar de container wordt gedeclareerd (bijvoorbeeld privéklasseleden als de container een privéklasselid is)

  3. Geassocieerde typen (zoals FooListPtr) gaan naar waar Foo wordt aangegeven, alshet bijbehorende type het primaire gebruik van het type is. Dat is bijna altijd waar voor sommige typen – b.v. shared_ptr.

  4. Als Fooeen aparte voorwaartse declaratiekop krijgt, en het bijbehorende type is daarmee in orde, wordt het ook naar de FooFwd.h verplaatst.

  5. Als het type alleen is gekoppeld aan een bepaalde interface (bijv. parameter voor een openbare methode), gaat het daarheen.

  6. Als het type wordt gedeeld (en niet voldoet aan een van de voorgaande criteria), krijgt het een eigen kop. Merk op dat dit ook betekent dat je alle afhankelijkheden moet binnenhalen.

Het voelt “voor de hand liggend” voor mij, maar ik ben het ermee eens dat het niet goed is als coderingsstandaard.


4, Autoriteit 7%

Helaas moet je bij typedefs kiezen tussen niet ideale opties voor je header-bestanden. Er zijn speciale gevallen waarin optie één (rechts in de klasse-header) goed werkt, maar het klinkt alsof het niet voor jou zal werken. Er zijn ook gevallen waarin de laatste optie goed werkt, maar meestal gebruikt u de subklasse om een patroon met een klasse te vervangen door een enkel lid van het type std::vector. Voor jouw situatie zou ik de forward-declaring header-oplossing gebruiken. Er is extra typen en overhead, maar anders zou het geen C++ zijn, toch? Het houdt dingen gescheiden, schoon en snel.

Other episodes