Der Weg, darüber nachzudenken, besteht darin, "wie ein Compiler zu denken".
Stellen Sie sich vor, Sie schreiben einen Compiler. Und Sie sehen Code wie diesen.
// file: A.h
class A {
B _b;
};
// file: B.h
class B {
A _a;
};
// file main.cc
#include "A.h"
#include "B.h"
int main(...) {
A a;
}
Wenn Sie die .cc- Datei kompilieren (denken Sie daran, dass die .cc und nicht die .h die Kompilierungseinheit ist), müssen Sie Speicherplatz für das Objekt zuweisen A
. Also, wie viel Platz dann? Genug zum Aufbewahren B
! Wie groß ist B
dann? Genug zum Aufbewahren A
! Hoppla.
Klar ein Zirkelverweis, den Sie brechen müssen.
Sie können es brechen, indem Sie dem Compiler erlauben, stattdessen so viel Speicherplatz zu reservieren, wie er über Upfront weiß. Zeiger und Referenzen sind beispielsweise immer 32 oder 64 Bit (abhängig von der Architektur), und wenn Sie (eines davon) durch ersetzen ein Zeiger oder eine Referenz, die Dinge wären großartig. Nehmen wir an, wir ersetzen in A
:
// file: A.h
class A {
// both these are fine, so are various const versions of the same.
B& _b_ref;
B* _b_ptr;
};
Jetzt ist es besser. Etwas. main()
sagt immer noch:
// file: main.cc
#include "A.h" // <-- Houston, we have a problem
#include
Für alle Bereiche und Zwecke (wenn Sie den Präprozessor herausnehmen) wird die Datei einfach in die .cc- Datei kopiert . Die .cc sieht also wirklich so aus:
// file: partially_pre_processed_main.cc
class A {
B& _b_ref;
B* _b_ptr;
};
#include "B.h"
int main (...) {
A a;
}
Sie können sehen, warum der Compiler damit nicht umgehen kann - er hat keine Ahnung, was es B
ist - er hat das Symbol noch nie zuvor gesehen.
Erzählen wir dem Compiler davon B
. Dies ist als Vorwärtserklärung bekannt und wird in dieser Antwort weiter erörtert .
// main.cc
class B;
#include "A.h"
#include "B.h"
int main (...) {
A a;
}
Das funktioniert . Es ist nicht großartig . Aber an diesem Punkt sollten Sie ein Verständnis für das Zirkelverweisproblem haben und wissen, was wir getan haben, um es zu "beheben", obwohl das Problem schlecht ist.
Der Grund, warum dieses Update schlecht ist, ist, dass die nächste Person #include "A.h"
deklarieren muss, B
bevor sie es verwenden kann, und einen schrecklichen #include
Fehler erhält . Verschieben wir also die Erklärung in Ah selbst.
// file: A.h
class B;
class A {
B* _b; // or any of the other variants.
};
Und in Bh können Sie an dieser Stelle einfach #include "A.h"
direkt.
// file: B.h
#include "A.h"
class B {
// note that this is cool because the compiler knows by this time
// how much space A will need.
A _a;
}
HTH.