Ich suche nach der Definition, wann ich eine Vorwärtsdeklaration einer Klasse in der Header-Datei einer anderen Klasse durchführen darf:
Darf ich dies für eine Basisklasse, für eine als Mitglied gehaltene Klasse, für eine Klasse tun, die als Referenz an die Mitgliedsfunktion übergeben wurde usw.?
c++
forward-declaration
c++-faq
Igor Oks
quelle
quelle
Antworten:
Versetzen Sie sich in die Position des Compilers: Wenn Sie einen Typ weiterleiten, weiß der Compiler nur, dass dieser Typ existiert. es weiß nichts über seine Größe, Mitglieder oder Methoden. Aus diesem Grund wird es als unvollständiger Typ bezeichnet . Daher können Sie den Typ nicht zum Deklarieren eines Mitglieds oder einer Basisklasse verwenden, da der Compiler das Layout des Typs kennen müsste.
Angenommen, die folgende Vorwärtserklärung.
Folgendes können und können Sie nicht tun.
Was Sie mit einem unvollständigen Typ tun können:
Deklarieren Sie ein Mitglied als Zeiger oder Verweis auf den unvollständigen Typ:
Deklarieren Sie Funktionen oder Methoden, die unvollständige Typen akzeptieren / zurückgeben:
Definieren Sie Funktionen oder Methoden, die Zeiger / Verweise auf den unvollständigen Typ akzeptieren / zurückgeben (jedoch ohne Verwendung seiner Elemente):
Was Sie mit einem unvollständigen Typ nicht machen können:
Verwenden Sie es als Basisklasse
Verwenden Sie es, um ein Mitglied zu deklarieren:
Definieren Sie Funktionen oder Methoden mit diesem Typ
Verwenden Sie seine Methoden oder Felder und versuchen Sie tatsächlich, eine Variable mit unvollständigem Typ zu dereferenzieren
Bei Vorlagen gibt es keine absolute Regel: Ob Sie einen unvollständigen Typ als Vorlagenparameter verwenden können, hängt davon ab, wie der Typ in der Vorlage verwendet wird.
Zum Beispiel
std::vector<T>
muss der Parameter ein vollständiger Typ sein, währendboost::container::vector<T>
dies nicht der Fall ist. Manchmal ist ein vollständiger Typ nur erforderlich, wenn Sie bestimmte Elementfunktionen verwenden. Dies ist beispielsweise der Fallstd::unique_ptr<T>
.Eine gut dokumentierte Vorlage sollte in ihrer Dokumentation alle Anforderungen ihrer Parameter angeben, einschließlich der Frage, ob es sich um vollständige Typen handeln muss oder nicht.
quelle
Die Hauptregel lautet, dass Sie nur Klassen vorwärts deklarieren können, deren Speicherlayout (und damit Elementfunktionen und Datenelemente) in der Datei, die Sie weiterleiten, nicht bekannt sein müssen.
Dies würde Basisklassen und alles andere als Klassen ausschließen, die über Referenzen und Zeiger verwendet werden.
quelle
Lakos unterscheidet zwischen Klassengebrauch
Ich habe es noch nie so prägnant ausgesprochen gesehen :)
quelle
Neben Zeigern und Verweisen auf unvollständige Typen können Sie auch Funktionsprototypen deklarieren, die Parameter angeben und / oder Werte zurückgeben, die unvollständige Typen sind. Sie können jedoch keine Funktion mit einem unvollständigen Parameter oder Rückgabetyp definieren , es sei denn, es handelt sich um einen Zeiger oder eine Referenz.
Beispiele:
quelle
Keine der bisherigen Antworten beschreibt, wann eine Vorwärtsdeklaration einer Klassenvorlage verwendet werden kann. Also, hier geht es.
Eine Klassenvorlage kann deklariert weitergeleitet werden als:
Im Anschluss an der Struktur der akzeptierten Antwort ,
Folgendes können und können Sie nicht tun.
Was Sie mit einem unvollständigen Typ tun können:
Deklarieren Sie ein Mitglied als Zeiger oder Verweis auf den unvollständigen Typ in einer anderen Klassenvorlage:
Deklarieren Sie ein Mitglied als Zeiger oder Verweis auf eine seiner unvollständigen Instanziierungen:
Deklarieren Sie Funktionsvorlagen oder Elementfunktionsvorlagen, die unvollständige Typen akzeptieren / zurückgeben:
Deklarieren Sie Funktionen oder Elementfunktionen, die eine ihrer unvollständigen Instanziierungen akzeptieren / zurückgeben:
Definieren Sie Funktionsvorlagen oder Elementfunktionsvorlagen, die Zeiger / Verweise auf den unvollständigen Typ akzeptieren / zurückgeben (jedoch ohne Verwendung seiner Elemente):
Definieren Sie Funktionen oder Methoden, die Zeiger / Verweise auf eine ihrer unvollständigen Instanziierungen akzeptieren / zurückgeben (jedoch ohne Verwendung ihrer Mitglieder):
Verwenden Sie es als Basisklasse einer anderen Vorlagenklasse
Verwenden Sie diese Option, um ein Mitglied einer anderen Klassenvorlage zu deklarieren:
Definieren Sie Funktionsvorlagen oder Methoden mit diesem Typ
Was Sie mit einem unvollständigen Typ nicht machen können:
Verwenden Sie eine seiner Instanziierungen als Basisklasse
Verwenden Sie eine seiner Instanziierungen, um ein Mitglied zu deklarieren:
Definieren Sie Funktionen oder Methoden mit einer ihrer Instanziierungen
Verwenden Sie die Methoden oder Felder einer ihrer Instanziierungen und versuchen Sie tatsächlich, eine Variable mit unvollständigem Typ zu dereferenzieren
Erstellen Sie explizite Instanziierungen der Klassenvorlage
quelle
X
undX<int>
genau gleich ist und nur die vorwärts deklarierende Syntax sich in irgendeiner wesentlichen Weise unterscheidet, wobei alle bis auf eine Zeile Ihrer Antwort nur Lucs und bedeutens/X/X<int>/g
? Wird das wirklich gebraucht? Oder habe ich ein kleines Detail übersehen, das anders ist? Es ist möglich, aber ich habe ein paar Mal visuell verglichen und kann keine sehen ...In einer Datei, in der Sie nur Zeiger oder Verweis auf eine Klasse verwenden. Und es sollte keine Member / Member-Funktion aufgerufen werden, wenn diese Zeiger / Verweise vorhanden sind.
mit
class Foo;
// VorwärtsdeklarationWir können Datenelemente vom Typ Foo * oder Foo & deklarieren.
Wir können Funktionen mit Argumenten und / oder Rückgabewerten vom Typ Foo deklarieren (aber nicht definieren).
Wir können statische Datenelemente vom Typ Foo deklarieren. Dies liegt daran, dass statische Datenelemente außerhalb der Klassendefinition definiert sind.
quelle
Ich schreibe dies als separate Antwort und nicht nur als Kommentar, da ich der Antwort von Luc Touraille nicht zustimme, nicht aus Gründen der Legalität, sondern wegen robuster Software und der Gefahr von Fehlinterpretationen.
Insbesondere habe ich ein Problem mit dem impliziten Vertrag darüber, was Benutzer Ihrer Benutzeroberfläche wissen müssen.
Wenn Sie Referenztypen zurückgeben oder akzeptieren, sagen Sie nur, dass sie einen Zeiger oder eine Referenz passieren können, die sie möglicherweise nur durch eine Vorwärtsdeklaration kennen.
Wenn Sie einen unvollständigen Typen kehren
X f2();
dann Anrufer sagen Sie müssen die vollständige Typangabe von X haben Sie brauchen es , um das LHS oder temporäres Objekt an der Aufrufstelle zu erstellen.Wenn Sie einen unvollständigen Typ akzeptieren, muss der Aufrufer das Objekt erstellt haben, das der Parameter ist. Selbst wenn dieses Objekt als ein anderer unvollständiger Typ von einer Funktion zurückgegeben wurde, benötigt die Aufrufsite die vollständige Deklaration. dh:
Ich denke, es gibt ein wichtiges Prinzip, dass ein Header genügend Informationen liefern sollte, um sie zu verwenden, ohne dass eine Abhängigkeit andere Header erfordert. Das bedeutet, dass der Header in eine Kompilierungseinheit aufgenommen werden kann, ohne einen Compilerfehler zu verursachen, wenn Sie deklarierte Funktionen verwenden.
Außer
Wenn diese externe Abhängigkeit gewünschtes Verhalten ist. Statt bedingte Kompilierung verwenden könnten Sie eine haben gut dokumentierte Anforderung für sie , ihre eigenen Header erklärt X. Diese liefern eine Alternative zu #ifdefs mit und kann eine nützliche Art und Weise sein Mocks oder andere Varianten vorstellen.
Der wichtige Unterschied sind einige Vorlagentechniken, bei denen ausdrücklich nicht erwartet wird, dass Sie sie instanziieren. Sie werden nur erwähnt, damit jemand nicht mit mir bissig wird.
quelle
I disagree with Luc Touraille's answer
Schreiben Sie ihm also einen Kommentar, einschließlich eines Links zu einem Blog-Beitrag, wenn Sie die Länge benötigen. Dies beantwortet die gestellte Frage nicht. Wenn sich alle Gedanken darüber machen würden, wie X funktioniert, begründete Antworten, die nicht mit X übereinstimmen, oder wenn wir über Grenzen debattieren, innerhalb derer wir unsere Freiheit, X zu verwenden, einschränken sollten, hätten wir fast keine wirklichen Antworten.Die allgemeine Regel, der ich folge, ist, keine Header-Datei einzuschließen, es sei denn, ich muss. Wenn ich also das Objekt einer Klasse nicht als Mitgliedsvariable meiner Klasse speichere, werde ich es nicht einschließen, sondern nur die Vorwärtsdeklaration verwenden.
quelle
Solange Sie die Definition nicht benötigen (denken Sie an Zeiger und Referenzen), können Sie mit Vorwärtsdeklarationen davonkommen. Aus diesem Grund werden sie meistens in Headern angezeigt, während Implementierungsdateien normalerweise den Header für die entsprechende (n) Definition (en) abrufen.
quelle
Normalerweise möchten Sie die Vorwärtsdeklaration in einer Klassenheaderdatei verwenden, wenn Sie den anderen Typ (Klasse) als Mitglied der Klasse verwenden möchten. Sie können nicht , die zukunfts angegebenen Klassen verwenden Methoden in der Header - Datei , weil C ++ nicht die Definition dieser Klasse zu diesem Zeitpunkt noch nicht kennt. Das ist Logik, die Sie in die CPP-Dateien verschieben müssen, aber wenn Sie Vorlagenfunktionen verwenden, sollten Sie diese auf den Teil reduzieren, der die Vorlage verwendet, und diese Funktion in den Header verschieben.
quelle
Nehmen wir an, dass die Vorwärtsdeklaration Ihren Code zum Kompilieren bringt (obj wird erstellt). Die Verknüpfung (exe-Erstellung) ist jedoch nur dann erfolgreich, wenn die Definitionen gefunden wurden.
quelle
class A; class B { A a; }; int main(){}
und lassen Sie mich wissen, wie das geht. Natürlich wird es nicht kompiliert. Alle richtigen Antworten hier erklären , warum und die genauen, beschränkt Kontexte , in denen zukunfts Erklärung ist gültig. Sie haben dies stattdessen über etwas völlig anderes geschrieben.Ich möchte nur eine wichtige Sache hinzufügen, die Sie mit einer weitergeleiteten Klasse tun können, die in der Antwort von Luc Touraille nicht erwähnt wird.
Was Sie mit einem unvollständigen Typ tun können:
Definieren Sie Funktionen oder Methoden, die Zeiger / Verweise auf den unvollständigen Typ akzeptieren / zurückgeben, und leiten Sie diese Zeiger / Verweise auf eine andere Funktion weiter.
Ein Modul kann ein Objekt einer vorwärts deklarierten Klasse an ein anderes Modul übergeben.
quelle
Wie Luc Touraille bereits sehr gut erklärt hat, wo die Vorwärtsdeklaration der Klasse verwendet werden soll und nicht.
Ich werde nur hinzufügen, warum wir es verwenden müssen.
Wir sollten nach Möglichkeit die Vorwärtsdeklaration verwenden, um die unerwünschte Abhängigkeitsinjektion zu vermeiden.
Da
#include
Header-Dateien zu mehreren Dateien hinzugefügt werden, wird beim Hinzufügen eines Headers zu einer anderen Header-Datei eine unerwünschte Abhängigkeitsinjektion in verschiedenen Teilen des Quellcodes hinzugefügt.#include
Dies kann vermieden werden, indem Header in.cpp
Dateien hinzugefügt werden, wo immer dies möglich ist, anstatt zu einer anderen Header-Datei und hinzuzufügen Verwenden Sie nach Möglichkeit die Klassenvorwärtsdeklaration in Header-.h
Dateien.quelle