Interne Typedefs in C ++ - guter oder schlechter Stil?


179

Ich habe in letzter Zeit häufig festgestellt, dass Typedefs für eine bestimmte Klasse innerhalb dieser Klasse relevant sind, d. H.

class Lorem
{
    typedef boost::shared_ptr<Lorem> ptr;
    typedef std::vector<Lorem::ptr>  vector;

//
// ...
//
};

Diese Typen werden dann an anderer Stelle im Code verwendet:

Lorem::vector lorems;
Lorem::ptr    lorem( new Lorem() );

lorems.push_back( lorem );

Gründe, warum es mir gefällt:

  • Es reduziert das durch die Klassenvorlagen verursachte Rauschen, std::vector<Lorem>wird Lorem::vectorusw.
  • Es dient als Absichtserklärung - im obigen Beispiel soll die Lorem-Klasse als Referenz gezählt boost::shared_ptrund in einem Vektor gespeichert werden.
  • Es ermöglicht eine Änderung der Implementierung - dh wenn Lorem geändert werden boost::intrusive_ptrmüsste, um zu einem späteren Zeitpunkt aufdringlich referenziert zu werden (via ), hätte dies nur minimale Auswirkungen auf den Code.
  • Ich denke, es sieht "hübscher" aus und ist wohl leichter zu lesen.

Gründe, warum ich es nicht mag:

  • Es gibt manchmal Probleme mit Abhängigkeiten - wenn Sie beispielsweise eine Lorem::vectorin eine andere Klasse einbetten möchten, aber nur Lorem deklarieren müssen (oder wollen) (anstatt eine Abhängigkeit von der Header-Datei einzuführen), müssen Sie am Ende die verwenden explizite Typen (zB boost::shared_ptr<Lorem>statt Lorem::ptr), was etwas inkonsistent ist.
  • Es kann nicht sehr häufig und daher schwerer zu verstehen sein?

Ich versuche, mit meinem Codierungsstil objektiv umzugehen, daher wäre es gut, andere Meinungen dazu einzuholen, damit ich mein Denken ein wenig analysieren kann.

Antworten:


153

Ich denke, es ist ein ausgezeichneter Stil und ich benutze ihn selbst. Es ist immer am besten, den Umfang der Namen so weit wie möglich einzuschränken, und die Verwendung von Klassen ist der beste Weg, dies in C ++ zu tun. In der C ++ Standard-Bibliothek werden beispielsweise Typedefs in Klassen häufig verwendet.


Das ist ein guter Punkt, ich frage mich, ob es „hübscher“ aussah, als mein Unterbewusstsein zart darauf hinwies, dass ein begrenzter Umfang gut ist Sache ist. Ich frage mich jedoch, ob die Tatsache, dass die STL sie überwiegend in Klassenvorlagen verwendet, sie zu einer subtil anderen Verwendung macht. Ist es schwieriger, in einer "konkreten" Klasse zu rechtfertigen?
Will Baker

1
Nun, die Standardbibliothek besteht eher aus Vorlagen als aus Klassen, aber ich denke, die Rechtfertigung ist für beide gleich.

14

Es dient als Absichtserklärung - im obigen Beispiel soll die Lorem-Klasse über boost :: shared_ptr referenzgezählt und in einem Vektor gespeichert werden.

Genau das macht es nicht .

Wenn ich 'Foo :: Ptr' im Code sehe, habe ich absolut keine Ahnung, ob es sich um ein shared_ptr oder ein Foo * handelt (STL hat :: pointer typedefs, die T * sind, denken Sie daran) oder was auch immer. Esp. Wenn es sich um einen gemeinsam genutzten Zeiger handelt, gebe ich überhaupt kein typedef an, aber behalte die Verwendung von shared_ptr explizit im Code.

Eigentlich verwende ich kaum Typedefs außerhalb der Vorlagen-Metaprogrammierung.

Die STL macht so etwas die ganze Zeit

Das STL-Design mit Konzepten, die in Bezug auf Elementfunktionen und verschachtelte Typedefs definiert sind, ist eine historische Sackgasse. Moderne Vorlagenbibliotheken verwenden freie Funktionen und Merkmalsklassen (vgl. Boost.Graph), da diese integrierte Typen nicht ausschließen Modellierung des Konzepts und weil es das Anpassen von Typen erleichtert, die nicht unter Berücksichtigung der Konzepte der angegebenen Vorlagenbibliotheken entworfen wurden.

Verwenden Sie die STL nicht als Grund, um dieselben Fehler zu machen.


Ich stimme Ihrem ersten Teil zu, aber Ihre letzte Bearbeitung ist etwas kurzsichtig. Solche verschachtelten Typen vereinfachen die Definition von Merkmalsklassen, da sie einen sinnvollen Standard darstellen. Betrachten Sie die neue std::allocator_traits<Alloc>Klasse ... Sie müssen sie nicht für jeden einzelnen Allokator, den Sie schreiben, spezialisieren, da sie die Typen einfach direkt ausleiht Alloc.
Dennis Zickefoose

@Dennis: In C ++ sollte die Benutzerfreundlichkeit auf der Seite des / Benutzers / einer Bibliothek liegen, nicht auf der Seite des / Autors /: Der Benutzer wünscht sich eine einheitliche Schnittstelle für ein Merkmal, und nur eine Merkmalsklasse kann dies geben. aus den oben genannten Gründen). Aber selbst als AllocAutor ist es nicht gerade schwieriger, sich std::allocator_traits<>auf seinen neuen Typ zu spezialisieren, als die erforderlichen Typedefs hinzuzufügen. Ich habe die Antwort auch bearbeitet, da meine vollständige Antwort nicht in einen Kommentar passte.
Marc Mutz - mmutz

Aber es ist auf der Seite des Benutzers. Als Benutzer, der allocator_traitsversucht, einen benutzerdefinierten Allokator zu erstellen, muss ich mich nicht mit den fünfzehn Mitgliedern der Merkmalsklasse befassen. Ich muss typedef Blah value_type;nur die entsprechenden Mitgliedsfunktionen sagen und bereitstellen, und die Standardeinstellung allocator_traitswird das herausfinden sich ausruhen. Schauen Sie sich außerdem Ihr Beispiel für Boost.Graph an. Ja, es wird häufig die Klasse der Merkmale verwendet ... aber die Standardimplementierung von graph_traits<G>einfachen Abfragen Gfür eigene interne Typedefs.
Dennis Zickefoose

1
Und selbst die Standardbibliothek 03 verwendet gegebenenfalls Merkmalsklassen. Die Philosophie der Bibliothek besteht nicht darin, Container generisch zu bearbeiten, sondern Iteratoren. Daher wird eine iterator_traitsKlasse bereitgestellt, damit Ihre generischen Algorithmen problemlos nach den entsprechenden Informationen fragen können. Dies führt wiederum standardmäßig dazu, dass der Iterator nach seinen eigenen Informationen abgefragt wird. Das lange und kurze daran ist, dass sich Merkmale und interne Typedefs kaum gegenseitig ausschließen ... sie unterstützen sich gegenseitig.
Dennis Zickefoose

1
@Dennis: iterator_traitswurde notwendig, weil T*es ein Modell von sein sollte RandomAccessIterator, aber Sie können die erforderlichen Typedefs nicht einfügen T*. Sobald wir iterator_traitsdas getan hatten , wurden die verschachtelten Typedefs überflüssig, und ich wünschte, sie wären dort und dann entfernt worden. Aus dem gleichen Grund (Unmöglichkeit, interne Typedefs hinzuzufügen) wird T[N]das STL- SequenceKonzept nicht modelliert , und Sie benötigen Kludges wie std::array<T,N>. Boost.Range zeigt, wie ein modernes Sequenzkonzept definiert werden T[N]kann, das modelliert werden kann, da weder verschachtelte Typedefs noch Elementfunktionen erforderlich sind.
Marc Mutz - mmutz

9

Typedefs sind diejenigen , auf denen richtlinienbasiertes Design und Merkmale in C ++ aufbauen. Die Leistungsfähigkeit der generischen Programmierung in C ++ beruht also auf Typedefs selbst.


8

Typdefs sind definitiv ein guter Stil. Und all deine "Gründe, die ich mag" sind gut und richtig.

Über Probleme, die Sie damit haben. Nun, die Vorwärtserklärung ist kein heiliger Gral. Sie können Ihren Code einfach entwerfen, um mehrstufige Abhängigkeiten zu vermeiden.

Sie können typedef außerhalb der Klasse verschieben, aber Class :: ptr ist so viel hübscher als ClassPtr, dass ich dies nicht tue. Es ist wie bei Namespaces wie bei mir - die Dinge bleiben im Rahmen verbunden.

Manchmal habe ich getan

Trait<Loren>::ptr
Trait<Loren>::collection
Trait<Loren>::map

Und es kann Standard für alle Domänenklassen und mit einer gewissen Spezialisierung für bestimmte sein.


3

Die STL erledigt diese Art von Dingen ständig - die Typedefs sind Teil der Schnittstelle für viele Klassen in der STL.

reference
iterator
size_type
value_type
etc...

sind alle Typedefs, die Teil der Schnittstelle für verschiedene STL-Vorlagenklassen sind.


Stimmt, und ich vermute, dass ich es hier zum ersten Mal aufgegriffen habe. Es scheint, als wären diese etwas einfacher zu rechtfertigen? Ich kann nicht anders, als Typedefs in einer Klassenvorlage als Variablen ähnlicher anzusehen, wenn Sie zufällig nach dem Vorbild der Metaprogrammierung denken.
Will Baker

3

Eine weitere Abstimmung dafür ist eine gute Idee. Ich habe damit begonnen, als ich eine Simulation geschrieben habe, die sowohl zeitlich als auch räumlich effizient sein musste. Alle Werttypen hatten ein Ptr-Typedef, das als gemeinsamer Boost-Zeiger begann. Ich habe dann einige Profile erstellt und einige davon in einen Boost-Intrusive-Zeiger geändert, ohne den Code ändern zu müssen, in dem diese Objekte verwendet wurden.

Beachten Sie, dass dies nur funktioniert, wenn Sie wissen, wo die Klassen verwendet werden sollen, und dass alle Verwendungen dieselben Anforderungen haben. Ich würde dies beispielsweise nicht im Bibliothekscode verwenden, da Sie beim Schreiben der Bibliothek nicht wissen können, in welchem ​​Kontext sie verwendet wird.


2

Derzeit arbeite ich an Code, der diese Art von Typedefs intensiv nutzt. Soweit ist das in Ordnung.

Aber ich habe festgestellt, dass es ziemlich oft iterative Typedefs gibt, die Definitionen auf mehrere Klassen aufgeteilt sind und man nie wirklich weiß, mit welchem ​​Typ man es zu tun hat. Meine Aufgabe ist es, die Größe einiger komplexer Datenstrukturen zusammenzufassen, die sich hinter diesen Typedefs verbergen. Daher kann ich mich nicht auf vorhandene Schnittstellen verlassen. In Kombination mit drei bis sechs Ebenen verschachtelter Namespaces wird es dann verwirrend.

Bevor Sie sie verwenden, müssen einige Punkte beachtet werden

  • Braucht noch jemand diese Typedefs? Wird die Klasse häufig von anderen Klassen verwendet?
  • Verkürze ich die Verwendung oder verstecke ich die Klasse? (Wenn Sie sich verstecken, können Sie auch an Schnittstellen denken.)
  • Arbeiten andere Leute mit dem Code? Wie machen Sie das? Werden sie denken, dass es einfacher ist oder werden sie verwirrt sein?

1

Ich empfehle, diese Typedefs außerhalb der Klasse zu verschieben. Auf diese Weise entfernen Sie die direkte Abhängigkeit von gemeinsam genutzten Zeiger- und Vektorklassen und können diese nur bei Bedarf einschließen. Sofern Sie diese Typen nicht in Ihrer Klassenimplementierung verwenden, sollten sie meiner Meinung nach keine inneren Typedefs sein.

Die Gründe, die Ihnen gefallen, stimmen immer noch überein, da sie durch das Typ-Aliasing durch typedef gelöst werden und nicht durch die Deklaration innerhalb Ihrer Klasse.


Das würde den anonymen Namespace mit den typedefs verschmutzen, nicht wahr?! Das Problem mit typedef ist, dass es den tatsächlichen Typ verbirgt, was zu Konflikten führen kann, wenn es in / von mehreren Modulen enthalten ist, die schwer zu finden / zu beheben sind. Es wird empfohlen, diese in Namespaces oder in Klassen zu enthalten.
Indy9000

3
Namenskonflikte und anonyme Namespace-Verschmutzung haben wenig damit zu tun, einen Typnamen innerhalb oder außerhalb einer Klasse zu behalten. Sie können einen Namenskonflikt mit Ihrer Klasse haben, nicht mit Ihren Typedefs. Verwenden Sie daher Namespaces, um Namensverschmutzungen zu vermeiden. Deklarieren Sie Ihre Klasse und die zugehörigen Typedefs in einem Namespace.
Cătălin Pitiș

Ein weiteres Argument für das Einfügen des Typedef in eine Klasse ist die Verwendung von Vorlagenfunktionen. Wenn eine Funktion beispielsweise einen unbekannten Containertyp (Vektor oder Liste) empfängt, der einen unbekannten Zeichenfolgentyp enthält (Zeichenfolge oder Ihre eigene Zeichenfolgen-konforme Variante). Die einzige Möglichkeit, den Typ der Containernutzlast herauszufinden, ist das typedef 'value_type', das Teil der Containerklassendefinition ist.
Marius

0

Wenn das typedef nur innerhalb der Klasse selbst verwendet wird (dh als privat deklariert wird), halte ich es für eine gute Idee. Aus genau den von Ihnen angegebenen Gründen würde ich es jedoch nicht verwenden, wenn die Typedefs außerhalb der Klasse bekannt sein müssen. In diesem Fall empfehle ich, sie außerhalb der Klasse zu verschieben.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.