Ist es eine gute Idee, Zeiger zu tippen?

75

Ich habe einen Code durchgesehen und festgestellt, dass die Konvention darin bestand, Zeigertypen wie zu drehen

SomeStruct* 

in

typedef SomeStruct* pSomeStruct;

Gibt es einen Verdienst dafür?

Unbekannt
quelle
1
Es ist häufig in C. Weil Sie versuchen, die Tatsache zu verbergen, dass es ein Zeiger ist. Dies ist das Objekt, das Sie an alle Schnittstellen in einer Bibliothek übergeben. In C ++ ist es nicht üblich und entmutigend, wenn auch nicht ungewöhnlich.
Martin York
3
Ein interessanter Artikel zu diesem allgemeinen Thema: Gespräche: Ein Sommernacht-Wahnsinn Siehe auch Linux-Kernel-Codierungsstil für eine extreme Ansicht von "Keine typedef-Zeiger (und keine typedefs für Strukturen oder Gewerkschaften)".
Jonathan Leffler

Antworten:

108

Dies kann angemessen sein, wenn der Zeiger selbst als "Black Box" betrachtet werden kann, dh als Datenelement, dessen interne Darstellung für den Code irrelevant sein sollte.

Wenn Ihr Code den Zeiger niemals dereferenziert und Sie ihn nur an API-Funktionen weitergeben (manchmal als Referenz), reduziert das typedef nicht nur die Anzahl der *s in Ihrem Code, sondern schlägt dem Programmierer auch vor, dass der Zeiger dies nicht tun sollte nicht wirklich eingemischt werden.

Dies macht es auch einfacher, die API in Zukunft zu ändern, wenn dies erforderlich ist. Wenn Sie beispielsweise eine ID anstelle eines Zeigers verwenden (oder umgekehrt), wird der vorhandene Code nicht beschädigt, da der Zeiger eigentlich nie dereferenziert werden sollte.

Artelius
quelle
9
Beispiel aus der Praxis: In OS X verwendet das CoreFoundation-Framework diese Technik in großem Umfang und bezeichnet sie als "undurchsichtige" Datentypen.
Hbw
3
Auch in OS X: pthread_t ist ein undurchsichtiger Typ; Es wird als Zeiger auf eine 'struct _opaque_pthread_t' geschrieben, die selbst ein undurchsichtiges Array von Bytes ist.
Adam Rosenfield
13
Gegenbeispiel: FILE * fp? Eigentlich stimme ich Ihnen zu, aber es gibt genügend Präzedenzfälle für das Gegenteil.
Jonathan Leffler
11
Wenn Sie dies tun, nennen Sie den undurchsichtigen Typ natürlich nicht "pFoo".
MSalters
3
@MattJoiner: Der Grund, warum FILE*nicht als undurchsichtiger Zeiger eingegeben wurde, besteht darin, Makros wie getc()und putc()die FILE-Struktur direkt zuzulassen und in den meisten Fällen den Aufwand für Funktionsaufrufe zu vermeiden.
Chqrlie
74

Nach meiner Erfahrung nicht. Durch das Ausblenden des ' *' ist der Code schwer zu lesen.

Sigjuice
quelle
1
Keine Ahnung davon. Ich meine, wenn Sie einen Zeiger haben, den wir sagen können, Deviceund Sie typedefihn auf Device_Ptr(anstatt ihn verwenden zu müssen Device *) oder ähnliches, ist er ziemlich lesbar. Ich sehe viele gut ausgereifte und ziemlich große Bibliotheken, die das tun. Insbesondere in C ++, wenn Sie Vorlagen hinzufügen, kann dies Ihren Fingern die Eingabe ersparen.
Rbaleksandar
@rbaleksandar wieder ist es in Ordnung, wenn Gerät * ein undurchsichtiger Griff ist.
Alex
Als C n00b denke ich, dass die Zeigersyntax von C ein Greuel ist - nur den unglücklichen Seelen gefallen, die bereits über 10 Jahre lang gelernt haben, sie zu mögen. Ich würde diesen Stern jeden Tag übernehmen myTypePtroder myTypeRefübernehmen. : P
Christoffer Bubach
28

Das einzige Mal, dass ich einen Zeiger innerhalb des typedef verwende, ist der Umgang mit Zeigern auf Funktionen:

typedef void (*SigCatcher(int, void (*)(int)))(int);

typedef void (*SigCatcher)(int);

SigCatcher old = signal(SIGINT, SIG_IGN);

Ansonsten finde ich sie eher verwirrend als hilfreich.


Die durchgestrichene Deklaration ist der richtige Typ für einen Zeiger auf die signal()Funktion, nicht auf den Signalfänger. Es könnte klarer gemacht werden (unter Verwendung des oben korrigierten SigCatcherTyps), indem geschrieben wird:

 typedef SigCatcher (*SignalFunction)(int, SigCatcher);

Oder um die signal()Funktion zu deklarieren :

 extern SigCatcher signal(int, SigCatcher);

Das heißt, a SignalFunctionist ein Zeiger auf eine Funktion, die zwei Argumente (an intund a SigCatcher) akzeptiert und a zurückgibt SigCatcher. Und signal()selbst ist eine Funktion, die zwei Argumente (an intund a SigCatcher) akzeptiert und a zurückgibt SigCatcher.

Jonathan Leffler
quelle
Funktioniert dieses Typedef mit weniger Interpunktion? "typedef void SigCatcher (int, void (*) (int)) (int)"
sigjuice
Nein; gcc sagt "Fehler: SigCatcher als Funktion deklariert, die eine Funktion zurückgibt".
Jonathan Leffler
Ich bin gerade als noch einfachere typedef'd Deklaration von signal () freebsd.org/cgi/man.cgi?query=signal
sigjuice
@Sigjuice: Auch ohne auf die BSD-Seite zu gehen (was ich erst getan habe, nachdem ich das Material bearbeitet hatte), sah ich große Probleme mit meiner Antwort, die jetzt behoben sind. Und was BSD nennt, sig_tpasst zu meinem SigCatcher, und ja, es vereinfacht die Erklärung von signal()enorm.
Jonathan Leffler
1
cool. Die äquivalente Deklaration mit dem expliziten * sieht meiner Meinung nach nicht so schlecht aus. typedef void SigCatcher (int); externes SigCatcher * -Signal (int, SigCatcher *);
Sigjuice
16

Dies kann Ihnen helfen, einige Fehler zu vermeiden. Zum Beispiel im folgenden Code:

int* pointer1, pointer2;

pointer2 ist kein int * , es ist einfach int . Aber mit typedefs wird das nicht passieren:

typedef int* pInt;
pInt pointer1, pointer2;

Sie sind beide jetzt int * .

Trauriger Entwickler
quelle
13
Wenn Sie den * berührenden Zeiger1 anstelle von "int" setzen, ist die Deklaration viel sinnvoller, dass der Zeiger2 kein Zeiger ist, sondern nur ein int.
Dreamlax
3
Ich denke, C-Programmierer würden argumentieren, dass die Verwendung des Dereferenzierungsoperators beim Deklarieren einer Zeigervariable eher eine idiomatische Sache ist. Zur Prüfung sagen sie in K & R: "'int * ip' als Mnemonik gedacht; es heißt, dass der Ausdruck '* ip' ein int ist."
Hbw
2
Aber jeder kennt diese Syntax, und es ist sowieso am besten, jeweils eine Variable zu deklarieren und zu initialisieren ... Ich schreibe selten, wenn überhaupt, int * pointer1, * pointer2; denn in der nächsten Zeile haben Sie zwei schöne Zeiger mit undefiniertem Wert.
Daniel Daranas
2
@ Daniel Daranas:int *p1 = NULL, *p2 = NULL;
Matt Joiner
2
Typisches Beispiel für die Verteidigung schlechter Praktiken mit noch mehr schlechten Praktiken. Deklarieren Sie niemals mehr als eine Variable in einer einzelnen Zeile, da es keinen Grund dafür gibt, Punkt. Es wird nur zu Fehlern führen. Und die ganze "Zeiger-berührende" Debatte ist Unsinn, denn wenn Sie überhaupt guten Code schreiben, ist der Ort des* keine Rolle.
Lundin
15

Meine Antwort ist ein klares "Nein".

Warum?

Zunächst einmal tauschen Sie einfach ein einzelnes Zeichen *gegen ein anderes einzelnes Zeichen aus p. Das ist null Verstärkung. Dies allein sollte Sie davon abhalten, dies zu tun, da es immer schlecht ist, zusätzliche Dinge zu tun, die sinnlos sind.

Zweitens, und das ist der wichtige Grund, der *trägt was bedeutet , dass zu verstecken ist nicht gut . Wenn ich etwas an eine solche Funktion übergebe

void foo(SomeType bar);

void baz() {
    SomeType myBar = getSomeType();
    foo(myBar);
}

Ich erwarte nicht, dass die Bedeutung von myBargeändert wird, indem man sie weitergibt foo(). Immerhin gehe ich an Wert vorbei, foo()sehe also immer nur eine Kopie von myBarrichtig? Nicht, wenn SomeTypeAlias ​​eine Art Zeiger bedeutet!

Dies gilt sowohl für C-Zeiger als auch für intelligente C ++ - Zeiger: Wenn Sie die Tatsache verbergen, dass es sich um Zeiger auf Ihre Benutzer handelt, entsteht Verwirrung, die völlig unnötig ist. Also bitte nicht alias deine Zeiger.

(Ich glaube, die Gewohnheit, Zeigertypen zu tippen, ist nur ein fehlgeleiteter Versuch, zu verbergen, wie viele Sterne man als Programmierer hat http://wiki.c2.com/?ThreeStarProgrammer .)

cmaster - Monica wieder einsetzen
quelle
+1. Ein weiterer Grund ist, dass es keinen Typsystemschutz vor void *vp=/*...*/; foo(vp);oder gibt foo(0);. (Typedefs für Aufzählungen und numerische Typedefs für Dinge, die niemals als Zahlen angezeigt werden sollten, haben ähnliche Probleme).
PSkocik
5

Das ist eine Frage des Stils. Sie sehen diese Art von Code sehr häufig in den Windows-Header-Dateien. Sie bevorzugen jedoch eher die Version in Großbuchstaben als das Präfix in Kleinbuchstaben p.

Persönlich vermeide ich diese Verwendung von typedef. Es ist viel klarer, wenn der Benutzer ausdrücklich sagt, dass er ein Foo * als PFoo möchte. Typedefs sind heutzutage am besten geeignet, um STL lesbar zu machen :)

typedef stl::map<stl::wstring,CAdapt<CComPtr<IFoo>> NameToFooMap;
JaredPar
quelle
5

Es kommt (wie so viele Antworten) darauf an.

In C ist dies sehr häufig, da Sie versuchen zu verschleiern, dass ein Objekt ein Zeiger ist. Sie versuchen zu implizieren, dass dies das Objekt ist, das alle Ihre Funktionen manipulieren (wir wissen, dass es ein Zeiger darunter ist, aber es repräsentiert das Objekt, das Sie manipulieren).

MYDB   db = MYDBcreateDB("Plop://djdjdjjdjd");

MYDBDoSomthingWithDB(db,5,6,7);
CallLocalFuc(db); // if db is not a pointer things could be complicated.
MYDBdestroyDB(db);

Unter MYDB befindet sich wahrscheinlich ein Zeiger auf ein Objekt.

In C ++ ist dies nicht mehr erforderlich.
Hauptsächlich, weil wir Dinge als Referenz weitergeben können und die Methoden in die Klassendeklaration aufgenommen werden.

MyDB   db("Plop://djdjdjjdjd");

db.DoSomthingWithDB(5,6,7);
CallLocalFuc(db);   // This time we can call be reference.
db.destroyDB();     // Or let the destructor handle it.
Martin York
quelle
5

Nein.

Es wird dein Leben unglücklich machen, sobald du es mischst const

typedef foo *fooptr;
const fooptr bar1;
const foo *bar2

Sind bar1und bar2der gleiche Typ?

Und ja, ich zitiere nur Herb Sutters Guru. Viel Wahrheit sprach sie. ;)

- Bearbeiten -

Link zum zitierten Artikel hinzufügen.

http://www.drdobbs.com/conversationsa-midsummer-nights-madness/184403835

dgnuff
quelle
Könnten Sie bitte einen Link zu dem Artikel hinzufügen, den Sie erwähnt haben? Außerdem ist er ein Mann! :-)
Fabio sagt Reinstate Monica
1
@ Azat Ibrakov Es ist ein kleiner Punkt, aber während Herb definitiv ein Typ ist, ist der Guru, über den er schreibt, weiblich. Daher ist es angebracht, sich auf sie im Weiblichen zu beziehen. Ja, ich weiß, dass sie für Herb tatsächlich ein Alter Ego ist, aber wenn Sie die gesamte Serie lesen, einschließlich der Geschichte, die in den ersten 20 Artikeln erzählt wurde, ist es für sie sinnvoll, so zu sein, wie sie ist.
dgnuff
4

Typedef wird verwendet, um die Lesbarkeit des Codes zu verbessern. Wenn Sie jedoch einen Zeiger als Typedef festlegen, wird die Verwirrung erhöht. Vermeiden Sie besser typedef Zeiger.

Chand
quelle
4

Die Diskussion wurde unter der Annahme geführt, dass die Sprache von Interesse C ist. Auswirkungen für C ++ wurden nicht berücksichtigt.

Verwenden eines Zeigertyps typedef für eine Struktur ohne Tags

Die Frage Größe einer Struktur, die als Zeiger definiert ist, wirft ein interessantes Seitenlicht auf die Verwendungtypedef von (Struktur-) Zeigern.

Betrachten Sie die Definition des taglosen konkreten (nicht undurchsichtigen) Strukturtyps:

typedef struct { int field1; double field2; } *Information;

Die Details der Mitglieder sind für diese Diskussion völlig tangential. Alles, was zählt, ist, dass dies kein undurchsichtiger Typ ist typedef struct tag *tag;(und Sie können solche undurchsichtigen Typen nicht über ein typedefohne Tag definieren).

Die Frage lautet: Wie können Sie die Größe dieser Struktur ermitteln?

Die kurze Antwort lautet "nur über eine Variable des Typs". Es gibt kein Tag, mit dem verwendet werden kann sizeof(struct tag). Sie können sizeof(*Information)zum Beispiel nicht sinnvoll schreiben undsizeof(Information *) die Größe eines Zeigers auf den Zeigertyp, nicht die Größe des Strukturtyps.

Wenn Sie eine solche Struktur zuweisen möchten, können Sie nur eine dynamische Zuordnung erstellen (oder Ersatztechniken, die die dynamische Zuordnung nachahmen). Es gibt keine Möglichkeit, eine lokale Variable des Strukturtyps zu erstellen, deren Zeiger aufgerufen werden Information, und es gibt auch keine Möglichkeit, einen Dateibereich (global oder) zu erstellenstatic ) des Strukturtyps , noch eine Möglichkeit, eine solche Struktur einzubetten (as im Gegensatz zu einem Zeiger auf eine solche Struktur) in eine andere Struktur oder einen anderen Vereinigungstyp.

Sie können - müssen - schreiben:

Information info = malloc(sizeof(*info));

Abgesehen von der Tatsache, dass der Zeiger in der versteckt ist typedef, ist dies eine gute Praxis - wenn die Art voninfo Änderungen ändert, bleibt die Größenzuweisung korrekt. In diesem Fall ist dies jedoch auch die einzige Möglichkeit, die Größe der Struktur zu ermitteln und die Struktur zuzuweisen. Und es gibt keine andere Möglichkeit, eine Instanz der Struktur zu erstellen.

Ist das schädlich?

Es hängt von Ihren Zielen ab.

Dies ist kein undurchsichtiger Typ - die Details der Struktur müssen definiert werden, wenn der Zeigertyp typedef'd' ist.

Dieser Typ kann nur mit dynamischer Speicherzuordnung verwendet werden.

Es ist ein Typ, der namenlos ist. Der Zeiger auf den Strukturtyp hat einen Namen, der Strukturtyp selbst jedoch nicht.

Wenn Sie die dynamische Zuweisung erzwingen möchten, scheint dies eine Möglichkeit zu sein.

Im Großen und Ganzen ist es jedoch wahrscheinlicher, Verwirrung und Angst zu verursachen als Erleuchtung.

Zusammenfassung

Es ist im Allgemeinen eine schlechte Idee typedef, einen Zeiger auf einen taglosen Strukturtyp zu definieren.

Jonathan Leffler
quelle
3

In diesem Fall können Sie keine STL-Container von const pSomeStruct erstellen, da der Compiler Folgendes liest:

list<const pSomeStruct> structs;

wie

list<SomeStruct * const> structs;

Dies ist kein legaler STL-Container, da die Elemente nicht zuweisbar sind.

Siehe diese Frage .

Dan Hook
quelle
Sicher, aber man würde niemals schreiben gehen list<const ordinary_type>, da es sowieso nicht funktionieren würde, also gibt es keine Verwirrung.
1
Ich bin mir nicht sicher, was du damit meinst ordinary_type. list<const SomeStruct*>ist ebenso gültig wie const SomeStruct*CopyAssignable.
Dan Hook
1

Die Win32-API erledigt dies mit nahezu jeder Struktur (wenn nicht allen).

POINT => *LPPOINT
WNDCLASSEX => *LPWNDCLASSEX
RECT => *LPRECT
PRINT_INFO_2 => *LPPRINT_INFO_2

Es ist schön, wie konsistent es ist, aber meiner Meinung nach fügt es keine Eleganz hinzu.

Dreamlax
quelle
Das liegt daran, dass Win32 eine C-API ist und es in C nicht C ++ üblich ist
Martin York
2
Die Frage ist sowohl mit C als auch mit C ++ gekennzeichnet. Was genau ist Ihr Punkt?
Dreamlax
1
Es sollte beachtet werden, dass dies auch in C nicht üblich ist. Die Win-API ist so ziemlich die einzige bekannte C-Bibliothek, die dies tut. Es wird auch die ungarische Notation verwendet. Es ist eine sehr obskure Bibliothek, was den Stil betrifft, es ist nicht etwas, das Sie als Kanon ansprechen sollten.
Lundin
1
Diese Notation geht auf den Ursprung von Windows vor 30 Jahren in der segmentierten Intel 16-Bit-Architektur zurück. LPsteht für langen Zeiger und die Kurzschreibweise LPPOINT ptr;war nichts weiter als das, eine handliche Abkürzung für POINT FAR *ptr;, FARErweiterung zu farund später zu __far. Das alles ist ziemlich unelegant, aber früher war es noch schlimmer.
Chqrlie
1

Der Zweck von typedef besteht darin, die Implementierungsdetails auszublenden, aber das Eingeben der Zeiger-Eigenschaft verbirgt zu viel und erschwert das Lesen / Verstehen des Codes. Also bitte tu das nicht.


Wenn Sie Implementierungsdetails ausblenden möchten (was häufig sinnvoll ist), verbergen Sie den Zeigerteil nicht. Nehmen Sie zum Beispiel den Prototyp für die Standardschnittstelle FILE:

FILE *fopen(const char *filename, const char *mode);
char *fgets(char *s, int size, FILE *stream);

Hier gibt fopen einen Zeiger auf eine Struktur zurück FILE(für die Sie die Implementierungsdetails nicht kennen). Vielleicht FILEist es kein so gutes Beispiel, weil es in diesem Fall mit einem pFILE-Typ hätte funktionieren können, der die Tatsache verbarg, dass es sich um einen Zeiger handelt.

pFILE fopen(const char *filename, const char *mode);
char *fgets(char *s, int size, pFILE stream);

Dies würde jedoch nur funktionieren, weil Sie nie mit dem Inhalt herumspielen, auf den direkt verwiesen wird. In dem Moment, in dem Sie einen Zeiger eingeben, den Sie an einigen Stellen ändern, wird der Code meiner Erfahrung nach sehr schwer zu lesen.

hlovdal
quelle
0

Vor einiger Zeit hätte ich diese Frage mit "Nein" beantwortet. Mit dem Aufkommen intelligenter Zeiger werden Zeiger nicht mehr immer mit einem Stern '*' definiert. Es ist also nichts Offensichtliches daran, dass ein Typ ein Zeiger ist oder nicht.

Jetzt würde ich also sagen: Es ist in Ordnung, Zeiger zu tippen, solange klargestellt wird, dass es sich um einen "Zeigertyp" handelt. Das bedeutet, dass Sie ein Präfix / Suffix speziell dafür verwenden müssen. Nein, "p" ist beispielsweise kein ausreichendes Präfix. Ich würde wahrscheinlich mit "ptr" gehen.

Benoît
quelle
Es sei denn, Sie beabsichtigen nicht, es als Zeiger zu verwenden. In diesem Fall können Sie es so benennen, wie Sie möchten. Ist es genau wichtig, was es ist, wenn es nur zum Weitergeben von Dingen verwendet wird (wie eine C-DATEI *)?
David Thornley
Guter Punkt. Ich habe versucht, einige Vor- und Nachteile in die Liste der Nachteile aufzunehmen, die gegen das Eingeben von Zeigern gesagt wurden ...
Benoît