2012-04-02 9 views
5

Ich bin neugierig, warum C++ nicht ungültig über definieren:Warum nimmt void in C++ keinen ungültigen Wert an?

typedef struct { } void; 

D.h. Was ist der Wert in einem Typ, der nicht instanziiert werden kann, selbst wenn diese Installation keinen Code erzeugen soll?

Wenn wir verwenden gcc -O3 -S, dann die beiden folgenden Produkte identisch Assembler:

int main() { return 0; } 

und

template <class T> T f(T a) { } 
typedef struct { } moo; 
int main() { moo a; f(a); return 0; } 

Dies macht durchaus Sinn. Ein struct { } nimmt einfach einen leeren Wert, einfach genug, um weg zu optimieren. In der Tat ist der komische Teil, dass sie anderen Code ohne die -O3 produzieren.

Sie können jedoch nicht denselben Trick mit einfach typedef void moo ziehen, weil Void keinen Wert annehmen kann, nicht einmal einen leeren. Hat diese Unterscheidung einen Nutzen?

Es gibt verschiedene andere stark typisierte Sprachen wie Haskell und vermutlich die MLs, die einen Wert für ihren Void-Typ haben, aber offenkundig keine wertlosen Typen bieten, obwohl einige native Pointer-Typen besitzen, die sich wie void * verhalten.

Antwort

4

ich die Gründe für void nicht in der Lage sehen, kommen aus den C Wurzeln von C++ instanziiert werden. In den alten blutigen Tagen war Typ Sicherheit keine große Sache und void* s wurden ständig herumgereicht. Sie können jedoch immer im Code suchen, die buchstäblich nicht sagen void* (wegen typedef s, Makros und in C++, Vorlagen), aber immer noch bedeutet es.

Es ist ein kleines bisschen Sicherheit, wenn Sie ein void* dereferenzieren können, aber es zu einem Zeiger auf einen richtigen Typ, zuerst umwandeln müssen. Ob Sie erreichen, dass eine unvollständige Art, indem man als Ben Voigt vermuten lassen, oder wenn Sie die eingebaute in void Typ verwenden, spielt keine Rolle. Sie sind vor falsch geschützt Raten, die Sie mit einem Typ beschäftigen, die Sie nicht sind.

Ja, führt void ärgerlich Sonderfälle, vor allem, wenn Vorlagen zu entwerfen. Aber es ist eine gute Sache (d. H. Absichtlich), dass der Compiler Versuche zur Instanziierung von void nicht stillschweigend akzeptiert.

+0

Ich stimme mit allem überein, bis auf den letzten Absatz. Die lästigen Spezialfälle, die es beim Umgang mit Templates einführt, sind absichtlich? Wozu? Um Programmierer zu nerven? –

+0

@ R.MartinhoFernandes: Nun, nicht absichtlich, dass es uns ärgert, sondern absichtlich in dem Sinne, dass Ihre Bibliothek in der Lage sein sollte, mit Typen umzugehen, die * nicht instanziiert werden können, wobei "void" das prominenteste Beispiel ist. Zum Beispiel, wenn Sie eine typsichere 'memcpy'-Vorlage geschrieben haben, die im Wesentlichen folgendes tut:' * to = * from'. Wenn du dann 'void' einfügst, * sollte * der Compiler deine Namen nennen, nicht stillschweigend akzeptieren, einfach weil der OP-Dummy-Wert für' void' nicht kopiert worden wäre, was du dir vorgestellt hast! – bitmask

+0

Oh, ich habe es dann falsch verstanden. –

3

Denn das würde nicht machen void einen unvollständigen Typ, jetzt wäre es?

Ihr Beispielcode Versuchen Sie es erneut mit:

struct x; // incomplete 
typedef x moo; 
+0

Ist 'moo' in meinem Beispiel ein unvollständiger Typ? Es funktioniert sehr gut die wenigen Orte, an denen ich es benutzt habe. –

+0

@Jeff: Nein, dein 'moo' ist ein vollständiger Typ. Meine ist unvollständig. 'void' ist ein unvollständiger Typ, der niemals vervollständigt werden kann. –

+0

Was würden Sie sagen, ist der Vorteil von "void" ein unvollständiger Typ? –

3

Warum solltevoid einen unvollständigen Typ sein?

Es gibt viele Gründe.

Am einfachsten ist dies: moo FuncName(...) noch etwas zurückgeben muss. Ob es ein neutraler Wert ist, ob es der "kein Wertwert" oder was auch immer ist; es muss immer noch return value; sagen, wobei value ein tatsächlicher Wert ist. Es muss return moo(); sagen.

Warum eine Funktion schreiben, die technisch etwas zurückgibt, wenn sie tatsächlich nichts zurückgibt? Der Compiler kann die Rückgabe nicht optimieren, da er einen Wert eines vollständigen Typs zurückgibt. Der Anrufer könnte es tatsächlich verwenden.

C++ ist nicht alle Vorlagen, weißt du. Der Compiler hat nicht unbedingt das Wissen, alles wegzuwerfen. Häufig müssen In-Function-Optimierungen durchgeführt werden, die keine Kenntnis von der externen Verwendung dieses Codes haben.

void bedeutet in diesem Fall bedeutet "Ich gebe nichts zurück." Es besteht ein grundlegender Unterschied zwischen diesem und "Ich gebe einen bedeutungslosen Wert zurück." Es ist legal, dies zu tun:

moo FuncName(...) { return moo(); } 

moo x = FuncName(...); 

Dies ist bestenfalls irreführend. Ein oberflächlicher Scan legt nahe, dass ein Wert zurückgegeben und gespeichert wird. Der Bezeichner x hat jetzt einen Wert und kann für etwas verwendet werden.

Während dies:

void FuncName(...) {} 

void x = FuncName(...); 

ist ein Compiler-Fehler. Das ist also:

void FuncName(...) {} 

moo x = FuncName(...); 

Es ist klar, was los ist: FuncName hat keinen Rückgabewert.

Auch wenn Sie C++ entwarfen von Grund auf, ohne Festnahmen von C, würden Sie noch einige Keyword müssen den Unterschied zwischen einer Funktion, um anzuzeigen, die einen Wert und eine zurückgibt, das nicht der Fall ist.

Darüber hinaus ist void* spezielle in Teil, weil void ist kein vollständiger Typ. Da der Standard vorschreibt, dass es sich nicht um einen vollständigen Typ handelt, kann das Konzept einer void* "Zeiger auf nicht typisierten Speicher" bedeuten. Dies hat Casting Semantik spezialisiert. Zeiger auf typisierten Speicher können implizit in nicht typisierten Speicher umgewandelt werden. Zeiger auf nicht typisierten Speicher können explizit in den typisierten Zeiger zurückversetzt werden, der sie einmal war.

Wenn Sie stattdessen moo* verwendet haben, wird die Semantik merkwürdig. Sie haben einen Zeiger auf typisiert Speicher. Derzeit definiert C++ das Umwandeln von nicht verwandten typisierten Zeigern (mit Ausnahme bestimmter Sonderfälle) zu undefiniertem Verhalten. Jetzt muss der Standard eine weitere Ausnahme für den Typ moo hinzufügen. Es hat zu sagen, was passiert, wenn Sie dies tun:

moo *m = new moo; 
*moo; 

Mit void, dies ist ein Compiler-Fehler ist. Was ist das mit moo? Es ist immer noch nutzlos und bedeutungslos, aber der Compiler muss es erlauben.

Um ehrlich zu sein, würde ich sagen, dass die bessere Frage wäre "Warum sollte void eine komplette Art sein?"

+0

Es gibt viele Situationen, in denen Sie den Parameter eines polymorphen Typs auf nichts spezialisieren möchten, weil dies logisch Sinn macht, den Code lesbarer macht, die Wiederverwendung von Code verbessert, etc. Ich bin nicht überrascht, dass Sie nicht aus einem 'void' handeln können zu einem 'moo', aber das würde in der Praxis niemals entstehen. –

+0

Um ein paar Extra-Boni hinzuzufügen: 'throw moo()', 'moo [5]' - alle Dinge, die nicht mit 'void' funktionieren, denn es ist unvollständig. – MSalters

+0

@JeffBurdges: Dann können Sie Ihre eigene leere "Nichts" -Klasse erstellen und tun, was Sie brauchen. –

Verwandte Themen