Ich weiß, dass ich tun kann:
class Foo;
Aber kann ich eine Klasse als von einer anderen geerbt deklarieren, wie:
class Bar {};
class Foo: public Bar;
Ein beispielhafter Anwendungsfall wären Co-Varianten-Referenzrückgabetypen.
// somewhere.h
class RA {}
class RB : public RA {}
... und dann in einem anderen Header, der irgendwo nicht enthalten ist.h.
// other.h
class RA;
class A {
public:
virtual RA* Foo(); // this only needs the forward deceleration
}
class RB : public RA; // invalid but...
class B {
public:
virtual RB* Foo(); //
}
Die einzige Information, die der Compiler benötigen sollte , um die Deklaration von zu verarbeiten RB* B:Foo()
, RB
ist die RA
als öffentliche Basisklasse. Jetzt brauchen Sie natürlich irgendwo.h, wenn Sie beabsichtigen, die Rückgabewerte von dereferenzieren Foo
. Wenn einige Clients jedoch nie anrufen Foo
, gibt es keinen Grund für sie, irgendwo.h einzuschließen, was die Kompilierung erheblich beschleunigen könnte.
Antworten:
Eine Forward-Deklaration ist nur dann wirklich nützlich, wenn Sie dem Compiler mitteilen möchten, dass eine Klasse mit diesem Namen vorhanden ist und an anderer Stelle deklariert und definiert wird. Sie können es in keinem Fall verwenden, in dem der Compiler Kontextinformationen über die Klasse benötigt, und es ist für den Compiler auch nicht von Nutzen, nur ein wenig über die Klasse zu erzählen. (Im Allgemeinen können Sie die Vorwärtsdeklaration nur verwenden, wenn Sie auf diese Klasse ohne anderen Kontext verweisen, z. B. als Parameter oder Rückgabewert.)
Daher können Sie Bar in keinem Szenario weiterleiten, in dem Sie Foo deklarieren, und es ist absolut nicht sinnvoll, eine Weiterleitungsdeklaration zu haben, die die Basisklasse enthält - was sagt Ihnen das außerdem nichts?
quelle
static_assert
oderenable_if
ein intelligenter Zeiger ihn auf bestimmte Typen beschränkt - Sie möchten den intelligenten Zeiger für einen unvollständigen Typ verwenden ... bam!limited private
- aber wir tun es nicht. Für die Pimpl-Sprache istunique_ptr
ein unvollständiger Typ das Beste, was wir in C ++ tun können.Vorwärtsdeklarationen sind Deklarationen, keine Definitionen. Alles, was die Deklaration einer Klasse erfordert (wie Zeiger auf diese Klasse), benötigt nur die Vorwärtsdeklaration. Alles, was die Definition erfordern würde - dh die tatsächliche Struktur der Klasse kennen müsste - würde jedoch nicht nur mit der Vorwärtsdeklaration funktionieren.
Abgeleitete Klassen müssen auf jeden Fall die Struktur ihres Elternteils kennen, nicht nur, dass das Elternteil existiert, daher wäre eine Vorwärtsdeklaration unzureichend.
quelle
*D
->*B
Konvertierung durchzuführen, während nur die Vererbungsstruktur bekannt ist. Ich gehe davon aus, dass es so aussieht, als würden die Strukturen zur Implementierung virtueller Basisklassen benötigt.Nein, es ist nicht möglich, die Vererbung weiterzuleiten, selbst wenn Sie nur mit Zeigern arbeiten. Bei Konvertierungen zwischen Zeigern muss der Compiler manchmal die Details der Klasse kennen, um die Konvertierung korrekt durchzuführen. Dies ist bei Mehrfachvererbung der Fall. (Sie können einige Teile der Hierarchie als Sonderfall verwenden, die nur eine einzelne Vererbung verwenden, aber nicht Teil der Sprache sind.)
Betrachten Sie den folgenden trivialen Fall:
#include <stdio.h> class A { int x; }; class B { int y; }; class C: public A, public B { int z; }; void main() { C c; A *pa = &c; B *pb = &c; C *pc = &c; printf("A: %p, B: %p, C: %p\n", pa, pb, pc); }
Die Ausgabe, die ich erhalten habe (mit 32 Bit Visual Studio 2010), ist:
A: 0018F748, B: 0018F74C, C: 0018F748
Für die Mehrfachvererbung muss der Compiler beim Konvertieren zwischen verwandten Zeigern eine Zeigerarithmetik einfügen, um die richtigen Konvertierungen zu erzielen.
Aus diesem Grund können Sie die Vererbung nicht weiterleiten, auch wenn Sie nur mit Zeigern arbeiten.
Warum dies nützlich wäre, würde die Kompilierungszeiten verbessern, wenn Sie Co-Varianten-Rückgabetypen anstelle von Casts verwenden möchten. Zum Beispiel wird dies nicht kompiliert:
class RA; class A { public: virtual RA *fooRet(); }; class RB; class B : public A { public: virtual RB *fooRet(); };
Aber das wird:
class RA; class A { public: virtual RA *fooRet(); }; class RA { int x; }; class RB : public RA{ int y; }; class B : public A { public: virtual RB *fooRet(); };
Dies ist nützlich, wenn Sie Objekte vom Typ B haben (keine Zeiger oder Referenzen). In diesem Fall ist der Compiler intelligent genug, um einen direkten Funktionsaufruf zu verwenden, und Sie können den Rückgabetyp von RB * direkt ohne Casting verwenden. In diesem Fall mache ich normalerweise den Rückgabetyp RA * und mache eine statische Umwandlung des Rückgabewerts.
quelle
Foo*
in aBar*
(oder Referenz) selbst mit a nützlich sein kann unvollständiger Typ.Alles, was Sie tun mussten, war,
RB
ohne das zu deklarieren: public RA
(oh, und auch;
am Ende Ihrer Klassendefinitionen hinzuzufügen ):class RA; class A { public: virtual RA* Foo(); }; class RB; class B { public: virtual RB* Foo(); }; // client includes somewhere.h class RA {}; class RB : public RA {}; int main () { return 0; }
Dies löst jedoch nicht das spezifische Problem, das in der Antwort von user1332054 gut beschrieben ist.
Einige der anderen Antworten scheinen einige Missverständnisse aufzuzeigen, die ich zerstreuen möchte:
Die Vorwärtsdeklaration ist auch dann nützlich , wenn wir wissen, dass die Definition wahrscheinlich nicht enthalten ist. Dies ermöglicht es uns, in unseren Bibliotheken viele Typabzüge vorzunehmen, die sie mit vielen anderen etablierten Bibliotheken kompatibel machen, ohne sie einzuschließen. Das Einschließen von Bibliotheken führt unnötigerweise zu zu vielen verschachtelten Includes, die die Kompilierungszeit sprengen können. Es wird empfohlen, Ihren Code gegebenenfalls kompatibel zu machen und so wenig wie möglich einzuschließen.
Typischerweise Sie können eine Klasse mit Zeigern auf Klassen definieren , die nur erklärt wurden und nicht definiert. Beispiel:
struct B; struct A { B * b_; B * foo () { return b_; } B & foo (B * b) { return *b; } }; int main () { return 0; }
Das obige Kompilieren funktioniert einwandfrei, da der Compiler nichts über B wissen muss.
Ein Beispiel, bei dem es möglicherweise etwas schwieriger ist zu erkennen, dass der Compiler weitere Informationen benötigt:
struct B; struct A { B * foo () { return new B; } };
Das obige Problem ist, weil
new B
derB::B()
Konstruktor aufgerufen wird, der noch nicht definiert wurde. Ebenfalls:struct B; struct A { void foo (B b) {} };
Hier
foo
muss der Kopierkonstruktor für aufgerufen werdenb
, der ebenfalls noch nicht definiert wurde. Zuletzt:struct B; struct A { B b; };
Hier haben wir implizit
A
mit dem Standardkonstruktor definiert , der den Standardkonstruktor des Aufrufs seiner Mitglieder aufruftb
, der noch nicht definiert wurde. Ich denke, du verstehst, worum es geht.In Bezug auf das allgemeinere Problem, das von user1332054 beschrieben wird, verstehe ich ehrlich gesagt nicht, warum es nicht möglich ist, Zeiger auf undefinierte Klassen in einer geerbten virtuellen Funktion zu verwenden.
Im weiteren Sinne denke ich jedoch, dass Sie es sich selbst schwerer machen, indem Sie Ihre Klassen definieren , anstatt sie nur zu deklarieren . Hier ist ein Beispiel, in dem Sie
DoCleverStuff
mit Ihren Klassen in Ihrer Bibliothek beginnen, bevor Sie überhaupt eine Ihrer Klassen definiert haben:// Just declare class RA; class RB; class A; class B; // We'll need some type_traits, so we'll define them: template <class T> struct is_type_A { static constexpr bool value = false; }; template <> struct is_type_A <A> { static constexpr bool value = true; }; template <class T> struct is_type_B { static constexpr bool value = false; }; template <> struct is_type_B <B> { static constexpr bool value = true; }; #include <type_traits> // With forward declarations, templates and type_traits now we don't // need the class definitions to prepare useful code: template<class T> typename std::enable_if<is_type_A<T>::value, RA *>::type DoCleverStuff (T & t) { // specific to A return t.fooRet(); } template<class T> typename std::enable_if<is_type_B<T>::value, RB *>::type DoCleverStuff (T & t) { // specific to B return t.fooRet(); } // At some point the user *does* the include: class RA { int x; }; class RB : public RA { int y; }; class A { public: virtual RA * fooRet() { return new RA; } }; class B : public A { public: virtual RB * fooRet() { return new RB; } }; int main () { // example calls: A a; RA * ra = DoCleverStuff(a); B b; RB * rb = DoCleverStuff(b); delete ra; delete rb; return 0; }
quelle
Ich denke nicht, dass es nützlich ist. Bedenken Sie: Sie haben eine Klasse definiert.
class Bar { public: void frob(); };
Jetzt deklarieren Sie eine Klasse Foo:
class Foo;
Mit Foo können Sie lediglich einen Zeiger darauf erstellen. Angenommen, Sie fügen die Informationen hinzu,
Foo
die abgeleitet sind vonBar
:class Foo: public Bar;
Was können Sie jetzt tun, was Sie vorher nicht konnten? Ich denke, Sie können nur einen Zeiger akzeptieren
Foo
und in einen Zeiger umwandelnBar
und dann diesen Zeiger verwenden.void frob(Foo* f) { Bar *b = (Bar)f; b->frob(); }
Sie müssen den Zeiger jedoch an einer anderen Stelle generiert haben, sodass Sie
Bar
stattdessen einfach einen Zeiger auf akzeptieren könnten .void frob(Bar* b) { b->frob(); }
quelle