Wenn eine Klassendeklaration eine andere Klasse nur als Zeiger verwendet, ist es sinnvoll, eine Klassenvorwärtsdeklaration zu verwenden, anstatt die Headerdatei einzuschließen, um Probleme mit zirkulären Abhängigkeiten vorbeugend zu vermeiden? also anstatt zu haben:
//file C.h
#include "A.h"
#include "B.h"
class C{
A* a;
B b;
...
};
Tun Sie dies stattdessen:
//file C.h
#include "B.h"
class A;
class C{
A* a;
B b;
...
};
//file C.cpp
#include "C.h"
#include "A.h"
...
Gibt es einen Grund, dies nicht zu tun, wo immer dies möglich ist?
c++
forward-declaration
Matte
quelle
quelle
delete
einen Zeiger nur mit einer Vorwärtsdeklaration verwenden können. Wenn die Klasse jedoch tatsächlich einen nicht trivialen Destruktor hat, erhalten Sie UB. Wenn alsodelete
"nur Zeiger verwendet", dann gibt es einen Grund. Wenn es nicht zählt, nicht so sehr.Antworten:
Die Vorwärtsdeklarationsmethode ist fast immer besser. (Ich kann mir keine Situation vorstellen, in der es besser ist, eine Datei einzuschließen, in der Sie eine Vorwärtsdeklaration verwenden können, aber ich werde nicht sagen, dass es für alle Fälle immer besser ist).
Es gibt keine Nachteile bei vorwärts deklarierten Klassen, aber ich kann mir einige Nachteile vorstellen, wenn Header unnötig eingefügt werden:
längere Kompilierungszeit, da alle Übersetzungseinheiten einschließlich
C.h
enthaltenA.h
, obwohl sie diese möglicherweise nicht benötigen.möglicherweise einschließlich anderer Header, die Sie nicht indirekt benötigen
Verschmutzung der Übersetzungseinheit mit Symbolen, die Sie nicht benötigen
Möglicherweise müssen Sie Quelldateien, die diesen Header enthalten, neu kompilieren, wenn er sich ändert (@PeterWood).
quelle
delete
Erklärung zu schreibenC.h
.Ja, die Verwendung von Vorwärtsdeklarationen ist immer besser.
Einige der Vorteile, die sie bieten, sind:
Durch das Vorwärtsdeklarieren einer Klasse wird diese bestimmte Klasse jedoch zu einem unvollständigen Typ, und dies schränkt die Operationen, die Sie für den unvollständigen Typ ausführen können, erheblich ein.
Sie können keine Operationen ausführen, bei denen der Compiler das Layout der Klasse kennen müsste.
Mit Unvollständiger Typ können Sie:
Mit unvollständigem Typ können Sie nicht:
quelle
Bequemlichkeit.
Wenn Sie vor der Phase wissen, dass jeder Benutzer dieser Header-Datei unbedingt auch die Definition von "
A
irgendetwas tun" (oder meistens) angeben muss. Dann ist es bequem, es nur ein für alle Mal einzuschließen.Dies ist ein ziemlich heikles Thema, da eine zu liberale Verwendung dieser Daumenregel einen nahezu unkompilierbaren Code ergibt. Beachten Sie, dass Boost das Problem anders angeht, indem es spezielle "Convenience" -Header bereitstellt, die einige enge Funktionen miteinander bündeln.
quelle
Ein Fall, in dem Sie keine Vorwärtsdeklarationen haben möchten, ist, wenn sie selbst schwierig sind. Dies kann passieren, wenn einige Ihrer Klassen Vorlagen enthalten, wie im folgenden Beispiel:
// Forward declarations template <typename A> class Frobnicator; template <typename A, typename B, typename C = Frobnicator<A> > class Gibberer; // Alternative: more clear to the reader; more stable code #include "Gibberer.h" // Declare a function that does something with a pointer int do_stuff(Gibberer<int, float>*);
Vorwärtsdeklarationen sind dasselbe wie Codeduplizierung: Wenn sich der Code häufig ändert, müssen Sie ihn jedes Mal an zwei oder mehr Stellen ändern, und das ist nicht gut.
quelle
namespace std { class string; }
wäre falsch, selbst wenn es erlaubt wäre, die Klassendeklaration in den Namespace std zu setzen, weil (ich denke) Sie ein typedef nicht legal vorwärts deklarieren können, als ob es eine Klasse wäre.Unterhaltsame Tatsache: In seinem C ++ - Styleguide empfiehlt Google,
#include
überall zu verwenden, um jedoch zirkuläre Abhängigkeiten zu vermeiden.quelle
Nein, explizite Vorwärtserklärungen sollten nicht als allgemeine Richtlinie betrachtet werden. Vorwärtsdeklarationen sind im Wesentlichen kopierte und eingefügte oder falsch geschriebene Codes, die, falls Sie einen Fehler darin finden, überall dort behoben werden müssen, wo die Vorwärtsdeklarationen verwendet werden. Dies kann fehleranfällig sein.
Um Nichtübereinstimmungen zwischen den "Vorwärts" -Deklarationen und ihren Definitionen zu vermeiden, fügen Sie Deklarationen in eine Header-Datei ein und fügen Sie diese Header-Datei sowohl in die definierenden als auch in die deklarationsverwendenden Quelldateien ein.
In diesem speziellen Fall, in dem nur eine undurchsichtige Klasse vorwärts deklariert ist, ist diese Vorwärtsdeklaration möglicherweise in Ordnung, aber im Allgemeinen kann "Vorwärtsdeklarationen anstelle von Includes verwenden, wann immer dies möglich ist", wie der Titel dieses Threads besagt sei ziemlich riskant.
Hier einige Beispiele für "unsichtbare Risiken" in Bezug auf Vorwärtsdeklarationen (unsichtbare Risiken = Deklarationsfehlanpassungen, die vom Compiler oder Linker nicht erkannt werden):
Explizite Vorwärtsdeklarationen von Symbolen, die Daten darstellen, können unsicher sein, da solche Vorwärtsdeklarationen möglicherweise die korrekte Kenntnis des Footprints (der Größe) des Datentyps erfordern.
Explizite Vorwärtsdeklarationen von Symbolen, die Funktionen darstellen, können ebenso unsicher sein wie die Parametertypen und die Anzahl der Parameter.
Das folgende Beispiel veranschaulicht dies, z. B. zwei gefährliche Vorwärtsdeklarationen von Daten sowie einer Funktion:
Datei ac:
#include <iostream> char data[128][1024]; extern "C" void function(short truncated, const char* forgotten) { std::cout << "truncated=" << std::hex << truncated << ", forgotten=\"" << forgotten << "\"\n"; }
Datei bc:
#include <iostream> extern char data[1280][1024]; // 1st dimension one decade too large extern "C" void function(int tooLarge); // Wrong 1st type, omitted 2nd param int main() { function(0x1234abcd); // In worst case: - No crash! std::cout << "accessing data[1270][1023]\n"; return (int) data[1270][1023]; // In best case: - Boom !!!! }
Kompilieren des Programms mit g ++ 4.7.1:
Hinweis: Unsichtbare Gefahr, da g ++ keine Compiler- oder
Linkerfehler / Warnungen ausgibt. Hinweis: Das Weglassen
extern "C"
führt zu einem Verknüpfungsfehlerfunction()
aufgrund des Mangels des C ++ - Namens.Ausführen des Programms:
> ./a.out truncated=abcd, forgotten="♀♥♂☺☻" accessing data[1270][1023] Segmentation fault
quelle
Absolut: Die Kapselung wird unterbrochen, indem der Benutzer einer Klasse oder Funktion die Implementierungsdetails kennen und duplizieren muss. Wenn sich diese Implementierungsdetails ändern, kann Code, der weiterleitet, beschädigt werden, während Code, der auf dem Header basiert, weiterhin funktioniert.
Vorwärts deklarieren einer Funktion:
setzt voraus, dass es als Funktion und nicht als Instanz eines statischen Funktorobjekts oder (keuchend!) eines Makros implementiert ist.
erfordert das Duplizieren der Standardwerte für Standardparameter.
erfordert die Kenntnis des tatsächlichen Namens und des Namespace, da es sich möglicherweise nur um eine
using
Deklaration handelt, die ihn in einen anderen Namespace zieht, möglicherweise unter einem Alias, undkann bei der Inline-Optimierung verlieren.
Wenn sich der konsumierende Code auf den Header stützt, können alle diese Implementierungsdetails vom Funktionsanbieter geändert werden, ohne dass Ihr Code beschädigt wird.
Vorwärts deklarieren einer Klasse:
erfordert das Wissen, ob es sich um eine abgeleitete Klasse handelt und von welcher Basisklasse (n) sie abgeleitet ist.
setzt voraus, dass Sie wissen, dass es sich um eine Klasse handelt und nicht nur um ein typedef oder eine bestimmte Instanziierung einer Klassenvorlage (oder dass Sie wissen, dass es sich um eine Klassenvorlage handelt und dass alle Vorlagenparameter und Standardwerte korrekt sind).
erfordert die Kenntnis des wahren Namens und Namespace der Klasse, da es sich möglicherweise um eine
using
Deklaration handelt, die sie in einen anderen Namespace zieht, möglicherweise unter einem Alias, underfordert die Kenntnis der richtigen Attribute (möglicherweise gelten spezielle Ausrichtungsanforderungen).
Wiederum unterbricht die Vorwärtsdeklaration die Kapselung dieser Implementierungsdetails, wodurch Ihr Code anfälliger wird.
Wenn Sie Header-Abhängigkeiten reduzieren müssen, um die Kompilierungszeit zu verkürzen, lassen Sie den Anbieter der Klasse / Funktion / Bibliothek einen speziellen Header für Vorwärtsdeklarationen bereitstellen. Die Standardbibliothek macht dies mit
<iosfwd>
. Dieses Modell bewahrt die Kapselung von Implementierungsdetails und gibt dem Bibliotheksverwalter die Möglichkeit, diese Implementierungsdetails zu ändern, ohne Ihren Code zu beschädigen, während gleichzeitig die Belastung des Compilers verringert wird.Eine andere Möglichkeit ist die Verwendung eines Pimpl-Idioms, das Implementierungsdetails noch besser verbirgt und Kompilierungen auf Kosten eines geringen Laufzeitaufwands beschleunigt.
quelle
Der einzige Grund, an den ich denke, ist, etwas Tippen zu sparen.
Ohne Vorwärtsdeklarationen können Sie die Header-Datei nur einmal einfügen, aber ich rate nicht dazu, dies bei ziemlich großen Projekten zu tun, da andere Personen auf Nachteile hinweisen.
quelle
Ja - Leistung. Klassenobjekte werden mit ihren Datenelementen zusammen im Speicher gespeichert. Wenn Sie Zeiger verwenden, wird der Speicher für das Objekt, auf das verwiesen wird, an einer anderen Stelle auf dem Heap gespeichert, normalerweise weit entfernt. Dies bedeutet, dass der Zugriff auf dieses Objekt einen Cache-Fehler verursacht und neu geladen wird. Dies kann in Situationen, in denen die Leistung entscheidend ist, einen großen Unterschied machen.
Auf meinem PC läuft die Faster () -Funktion ca. 2000x schneller als die Slower () -Funktion:
class SomeClass { public: void DoSomething() { val++; } private: int val; }; class UsesPointers { public: UsesPointers() {a = new SomeClass;} ~UsesPointers() {delete a; a = 0;} SomeClass * a; }; class NonPointers { public: SomeClass a; }; #define ARRAY_SIZE 100000 void Slower() { UsesPointers list[ARRAY_SIZE]; for (int i = 0; i < ARRAY_SIZE; i++) { list[i].a->DoSomething(); } } void Faster() { NonPointers list[ARRAY_SIZE]; for (int i = 0; i < ARRAY_SIZE; i++) { list[i].a.DoSomething(); } }
In Teilen von Anwendungen, die leistungskritisch sind oder an Hardware arbeiten, die besonders anfällig für Cache-Kohärenzprobleme ist, können Datenlayout und -nutzung einen großen Unterschied machen.
Dies ist eine gute Präsentation zum Thema und zu anderen Leistungsfaktoren: http://research.scee.net/files/presentations/gcapaustralia09/Pitfalls_of_Object_Oriented_Programming_GCAP_09.pdf
quelle