2009-03-06 4 views
1

Ich habe drei Iterationen des folgenden einfachen Programms ausprobiert. Dies ist ein sehr vereinfachter Versuch, ein Container-und-Iterator-Klassenpaar zu schreiben, aber ich stieß auf Probleme mit unvollständigen Typen (Forward-Deklarationen). Ich entdeckte, dass dies tatsächlich möglich war, nachdem ich alles templatiert hatte - aber nur, wenn ich tatsächlich den Template-Parameter verwendete! (Ich erkannte dies durch Blick auf die Google sparsetable code.)Warum lassen sich mit C++ - Vorlagen unvollständige Typen umgehen (Forward-Deklarationen)?

Alle Hinweise, warum die zweite funktioniert, während die dritte nicht funktioniert? (Ich weiß, warum der erste nicht funktioniert - der Compiler muss das Speicherlayout des Containers kennen.)

Vielen Dank im Voraus.

// This doesn't work: invalid use of incomplete type. 
#if 0 
struct container; 
struct iter { 
    container &c; 
    int *p; 
    iter(container &c) : c(c), p(&c.value()) {} 
}; 
struct container { 
    int x; 
    int &value() { return x; } 
    iter begin() { return iter(*this); } 
}; 
int main() { 
    container c; 
    c.begin(); 
    return 0; 
} 
#endif 

// This *does* work. 
template<typename T> struct container; 
template<typename T> struct iter { 
    container<T> &c; 
    T *p; 
    iter(container<T> &c) : c(c), p(&c.value()) {} 
}; 
template<typename T> struct container { 
    T x; 
    T &value() { return x; } 
    iter<T> begin() { return iter<T>(*this); } 
}; 
int main() { 
    container<int> c; 
    c.begin(); 
    return 0; 
}; 

// This doesn't work either. 
#if 0 
template<typename T> struct container; 
template<typename T> struct iter { 
    container<int> &c; 
    int *p; 
    iter(container<int> &c) : c(c), p(&c.value()) {} 
}; 
template<typename T> struct container { 
    int x; 
    int &value() { return x; } 
    iter<int> begin() { return iter<int>(*this); } 
}; 
int main() { 
    container<int> c; 
    c.begin(); 
    return 0; 
} 
#endif 
+0

Könnten Sie genauer sein, was "nicht funktioniert" eigentlich bedeutet? Kann es nicht kompiliert werden, und wenn ja, was ist die Nachricht? Läuft es nicht richtig, und wenn ja, was macht es, von dem Sie das nicht erwartet haben? –

+0

Es kann nicht kompiliert werden, mit der Nachricht, die ich angegeben habe: "ungültige Verwendung von unvollständigem Typ." – Yang

Antwort

4

Die erste erfordert eine Definition von container, da Sie eine Kopieroperation ausführen. Wenn Sie den Konstruktor iter nach der Definition container definieren, wären Sie in Ordnung. Also:

struct container; 
struct iter { 
    container &c; 
    int *p; 
    iter(container &c); 
}; 

struct container { 
    int x; 
    int &value() { return x; } 
    iter begin() { return iter(*this); } 
}; 

iter::iter(container &c) : c(c), p(&c.value()) {} 

int main() { 
    container c; 
    c.begin(); 
    return 0; 
} 

Das zweite Beispiel funktioniert, weil es keine Klasse ist, bis Sie tatsächlich in Ihrem main Funktion instanziiert. Zu diesem Zeitpunkt sind alle Typen definiert. Versuchen Sie, eine der iter oder container Vorlagen Definition nach Main zu verschieben, und Sie werden einen Fehler treffen.

Das dritte Beispiel ist eine Spezialisierung für int oder so scheint es. Dies sollte kompiliert werden, da der Vorlagenparameter für iter nicht verwendet wird. Sie haben die Spezialisierungssyntax ein bisschen aus. Allerdings gibt es keinen richtigen Konstruktor, so dass Sie nur Müll für x erhalten. Außerdem werden Iteratoren durch Zeiger gut modelliert. Der this Wert wird nicht viel helfen. Iteratoren werden normalerweise für eine Sequenz und nicht für ein einzelnes Objekt benötigt. Es gibt jedoch nichts, was dich daran hindern könnte, eins zu bauen.

Und Sie brauchen keine ; nach einem Funktionskörper.

+0

Meine zwei Cent - Sie erwähnten "Weitergabe dieses Wertes", und ein wenig mehr Details könnten helfen. Wenn Sie "* this" zurückgeben/übergeben, haben Sie eine Referenz, +/- abgeleitete Qualifier. Wenn Sie 'this' zurückgeben, haben Sie einen rohen Zeiger auf das aktuelle Objekt, und Sie müssen sich Sorgen um die Lebensdauer des Objekts machen.Wenn Sie einen Zeiger benötigen, um zwischen Kontexten zu wechseln, was hauptsächlich für dynamisches Speichermanagement gilt, empfiehlt sich ein intelligenter (eindeutiger/gemeinsamer) Zeiger oder ein vergleichbarer RAII-Typ. (Sie können 'this' noch übergeben, aber idealerweise nur zum Blockieren von Funktionen.) Alles hinzufügen/korrigieren? –

2

Sie können dies durch die Definition von iter ohne Vorlagen tun :: iter() nach Definition des Behältnisses:

struct container; 

struct iter { 
    container &c; 
    int *p; 
    iter(container &c); 
}; 

struct container { 
    int x; 
    int &value() { return x; } 
    iter begin() { return iter(*this); } 
}; 

iter::iter(container &c) 
    : c(c), p(&c.value()) {} 

int main() { 
    container c; 
    c.begin(); 
    return 0; 
} 

Template-Version funktioniert, weil, wenn Sie Vorlagen beiden Klassen instanziiert werden vollständig definiert.

0

Im ersten Fall versuchen Sie, auf eine Elementfunktion der Container-Klasse zuzugreifen, bevor die Klasse definiert wurde. Dies funktioniert nicht.

Im zweiten Fall wird die Vorlage bei der ersten Verwendung mit einem bestimmten Typ instanziiert. Zu diesem Zeitpunkt wurde die Container-Klasse in main definiert und kompiliert.

Im dritten Fall gibt es einen Zirkelverweis. container verwendet iter und iter verwendet container, so dass es nicht funktionieren kann.