Wie funktioniert Typedef?



  • Ich verstehe typedef nicht!

    typedef int muh
    muh var1;
    

    Ist noch klar, denn das verstehe ich einfach als Ersetzung. Sobald es aber an Zeiger oder gar Funktionszeiger geht, sieht alles Banane aus. 😮

    Hier ein paar Beispiele:

    string *const cstr;
    
    typedef string *pstring;
    const pstring cstr2;
    

    Wäre es nur eine Ersetzung, wäre cstr2 ein Zeiger auf const. Tatsächlich ist es aber ein konstanter Zeiger wie in Zeile 1 definiert ist. Warum ist das so?

    // Funktionsdeklaration ff(int), Rückgabetyp ist ein Funktionszeiger auf int (*)(int *, int)
    int (*ff(int))(int*, int);
    
    // Ersatz mit typedef
    typedef int (*PF) (int*, int);
    PF ff(int);
    

    Der Typedef macht das eigentlich recht verständlich, verwirrend ist aber, dass die Deklartion eigentlich die oben genannte Form haben muss und es deshalb nicht wie der Typedef es für mich darstellt "int (*PF) (int*, int) ff(int);" heißt.

    Wie kann man das korrekt lesen? Warum und wie ergeben sich diese Zusammenhänge?



  • Typo3 schrieb:

    string *const cstr;
    
    typedef string *pstring;
    const pstring cstr2;
    

    Wäre es nur eine Ersetzung, wäre cstr2 ein Zeiger auf const. Tatsächlich ist es aber ein konstanter Zeiger wie in Zeile 1 definiert ist. Warum ist das so?

    pstring ist ein typedef auf den Typ 'Zeiger auf string', also ist const pstring der Typ 'konstanter Zeiger auf string'.

    // Funktionsdeklaration ff(int), Rückgabetyp ist ein Funktionszeiger auf int (*)(int *, int)
    int (*ff(int))(int*, int);
    
    // Ersatz mit typedef
    typedef int (*PF) (int*, int);
    PF ff(int);
    

    Der Typedef macht das eigentlich recht verständlich, verwirrend ist aber, dass die Deklartion eigentlich die oben genannte Form haben muss und es deshalb nicht wie der Typedef es für mich darstellt "int (*PF) (int*, int) ff(int);" heißt.

    Im Grunde sind typedefs ganz einfach zu lesen: Sie sehen genauso aus wie andere Deklarationen, nur dass da noch typedef davorsteht. Das zu deklarierende wird dadurch statt zu einer Variable oder Funktion zu einem Typ. Und Deklarationen liest man so:

    1. Trennung in Typ und Deklarator
    Beispiele:
    int x; Typ int, Deklarator x, Aussage: x hat den Typ int
    int* x; Typ: int, Deklarator *x, Aussage: *x hat den Typ int
    int f(); Typ: int, Deklarator: f(), Aussage: f() hat den Typ int

    2. Aus dem Typ des Deklarators folgern, welchen Typ das deklarierte Objekt hat

    Beispiele:
    int x; => x hat den Typ int
    int* x; => Wenn ich x dereferenziere, bekomme ich x, also hat x den Typ Zeiger auf int
    int f(); => Wenn ich f mit () aufrufe, ergibt sich int, also ist f eine Funktion mit der Argumentliste (), die int zurückliefert

    typedef int (*PF)(int*, int);
    

    (*PF)(int*, int) ist der Deklarator, das soll int ergeben.
    Folgerungen:
    (*PF) ist eine Funktion mit der Argumentliste (*int, int)
    PF ist ein Zeiger

    Zusammen: PF ist ein Zeiger auf eine Funktion mit der Argumentliste (*int, int), die int zurückliefert.

    int (*ff(int))(int*, int);
    

    (*ff(int))(int*,int) soll int sein
    Folgerungen:
    (*ff(int)) ist eine Funktion mit der Argumentliste (int*,int)
    ff(int) ist ein Zeiger
    ff ist eine Funktion mit der Argumentliste (int)

    Zusammen: ff ist eine Funktion mit (int), die einen Zeiger zurückliefert auf eine Funktion mit der Argumentliste (int*,int), die int zurückliefert.



  • Typo3 schrieb:

    ...Warum ist das so?...

    Weil ein typedef eben nicht dasselbe ist wie ein #define.
    Es "bindet stärker" als ein const....

    Ist übrigens ein Grund, warum ich immer dringend von "Pointer-typedefs" abrate (auch, wenn sie nicht ganz so gräßlich sind wie "#define-typedefs"): Sie verhalten sich nicht immer so, wie erwartet.

    typedef string STRING1;
    typedef char* STRING2;
    
    void f(const STRING1);
    void f(const STRING2);
    

    .. die beiden f haben unterschiedliche Parametersemantik !

    Gruß,

    Simon2.



  • Simon2 schrieb:

    Ist übrigens ein Grund, warum ich immer dringend von "Pointer-typedefs" abrate

    Mal davon abgesehen, dass ich den Nachteil nicht verstehe (das ist doch vielmehr das intuitive Verhalten): Was bietest Du als Alternative?

    Ich benötige die Zeiger-Typedefs einfach, weil ich den Zeiger-Typ austauschbar halten muss. Daher habe ich Folgendes in einem Code:

    // config.hpp, allgemeine Konfiguration des Projekts:
    
    // Diese Definition kann sich ändern!
    template <typename T>
    struct ptr {
        typedef boost::shared_ptr<T> type;
    };
    
    // fwd.hpp, enthält einige Vorwärtsdeklarationen:
    
    #include "config.hpp"
    
    struct expression;
    struct reference;
    struct value;
    // …
    
    #define EXPR_PTR_ALIAS(name) typedef ptr<name>::type p_##name;
    
    EXPR_PTR_ALIAS(expression)
    EXPR_PTR_ALIAS(reference)
    EXPR_PTR_ALIAS(value)
    
    #undef EXPR_ALIAS_PTR
    

    … wie könnte ich das besser machen? Überall die ausführliche Schreibweise ptr<xyz>::type zu verwenden, bläht den Code unheimlich auf und macht ihn extrem unleserlich (ich habe es versucht – es war einfach furchtbar).



  • @Konrad

    Sehe ich auch so, was wuerde man in der generischen Programmierung unter C++ ohne typedefs machen?!

    Edit: Simon2 redet aber auch nur von "Pointer-Typedefs". Und ein shared_ptr<irgendwas> ist kein Pointer, genauso wie ein std::vector kein array ist.

    Edit2: @Simon2 Ich komm mit deiner Aussage ueber "Pointer-Typedefs" nicht ganz klar. Kannst du das bitte mal fuer mich praezisieren bzw. mal ein konkretes Codebeispiel geben, bei denen die beiden typedefs zu Problemen fuehren. Danke 🙂



  • Simon2 schrieb:

    Ist übrigens ein Grund, warum ich immer dringend von "Pointer-typedefs" abrate (auch, wenn sie nicht ganz so gräßlich sind wie "#define-typedefs"): Sie verhalten sich nicht immer so, wie erwartet.

    typedef char* STRING2;
    

    Ich glaube das Problem hier ist, dass du durch einen Typedef die speziellen Eigenschaften eines char-Pointers für den Leser unsichtbar werden lässt, so dass er mit falschen Erwartungen an diesen Typ herangeht. Mit typedefs auf Pointer hat das an sich nichts zu tun, nur mit sinnvoller Namensgebung.

    Davon abgesehen ist es natürlich oft ein Rückschlag in längst vergangene Pascal-Tage, zwanghaft typedefs auf Pointer einzuführen. Man sieht das zum Glück immer seltener.



  • Gut, das sehe ich auch so mit der sinnvollen Namensgebung. BTW ich finde deine Signatur toll Bashar 🙂



  • um die scheinbare Ungereimtheit mit den Pointertypedefs und anschließender const-Deklaration zu vermeiden, kann man es sich angewöhnen, const grundsätzlich nach dem als const zu deklarierenden Typen zu schreiben, also statt

    const int i;
    

    dann

    int const;
    

    . Dann wird aus typedefs nämlich

    typedef int* Iptr;
    Iptr const iptr; //klar, das ist int* const, also ein constanter Zeiger auf int
    

    Zugegeben, ich habs bisher erst in einem Buch gesehn, dass das auch umgesetzt wurde (Josuttis/Vandervoorde, "C++ Templates, a complete guide"), aber es öst eben das Problem. Ob es sinnvoll ist, das umzusetzen, sei jedem selbst überlassen, allerdings empfinde ich persönlich Dinge wie

    foo foo::operator=(foo const& other); //seltsame Art, eine const reference zu deklarieren)
    

    als die größere Stolperfalle beim Lesen im Vergleich zu den doch eher selten vorkommenden Pointer typedefs.



  • Bashar schrieb:

    ...
    Ich glaube das Problem hier ist, dass du durch einen Typedef die speziellen Eigenschaften eines char-Pointers für den Leser unsichtbar werden lässt, so dass er mit falschen Erwartungen an diesen Typ herangeht. ...

    Genau DAS meinte ich: Das "Verstecken" der Pointersemnatik des Typs.
    Passiert natürlich viel bei char*, ich habe es aber auch schon öfter bei anderen Typen gesehen (2. Rang: void, gibt aber noch mehr).

    Und das (also das Verstecken) finde ich fatal.

    Wenn aus dem Typ(namen) noch klar hervorgeht, dass er als Zeiger zu handeln ist, finde ich das voll OK. (z.B bei Funktionspointern erhöht IMHO oftmals die Lesbarkeit, ohne eine andere Semantik zu suggerieren).

    Gruß,

    Simon2.



  • pumuckl schrieb:

    Ob es sinnvoll ist, das umzusetzen, sei jedem selbst überlassen, allerdings empfinde ich persönlich Dinge wie

    foo foo::operator=(foo const& other); //seltsame Art, eine const reference zu deklarieren)
    

    als die größere Stolperfalle beim Lesen im Vergleich zu den doch eher selten vorkommenden Pointer typedefs.

    Stolperfalle? Ich mache das immer so. Und zwar aus dem ganz einfachen Grund, dass ich es einheitlicher finde, 'const' stets postfix anzugeben, denn bei Funktionsdeklarationen steht 'const' ja auch postfix, und wenn man da mischt, sieht das komisch aus:

    void f(const t&) const;
    // versus
    void g(t const&) const;
    


  • Wie gut, daß C bzw. C++ beides erlaubt (const als Prä- und Postfix).
    So kann es jeder machen, wie er will und man kann sich schön über den "unleserlichen" Code von anderen aufregen 😃

    P.S. Ich persönlich nutze 'const' als Präfix, quasi als einleitendes Schlüsselwort.
    So daß ein konstanter Zeiger auf ein konstantes Objekt dann quasi symmetrisch ist:

    const A * const pA; // Edit: * entfernt (wollte nur einen Einfach-Zeiger darstellen)
    


  • Th schrieb:

    const A * const * a;
    

    Was dann ein non-const-Zeiger auf einen const-Zeiger auf ein const-Objekt waere 😉
    Ist denke ich mal Geschmackssache, wie man es nun macht. Man wird sich dran gewoehnen muessen, dass man es mal als Prae- und mal als Postfix zu lesen bekommt, wenn man sich fremden Code anschaut. Fuer sich selber sollte man es auf jeden Fall konsistent halten, wenn man mit mehreren an einem groesseren Projekt arbeitet moeglichst projektweit einheitlich.



  • Th schrieb:

    Wie gut, daß C bzw. C++ beides erlaubt (const als Prä- und Postfix)...

    Naja ... aber nur auf den ersten Qualifizierer. Ansonsten ist es ja immer postfix...

    Vielleicht wäre es schon, es IMMER als Präfix zu haben, da es aber nunmal so ist, wie es ist, präferiere ich da auch ein einheitliches Postfix.

    Gruß,

    Simon2.


Anmelden zum Antworten