Warum muss ich über den this-Zeiger auf Template-Basisklassenmitglieder zugreifen?

Lesezeit: 10 Minuten

Warum muss ich uber den this Zeiger auf Template Basisklassenmitglieder zugreifen
Ali

Wenn die folgenden Klassen keine Vorlagen wären, könnte ich sie einfach haben x in dem derived Klasse. Mit dem folgenden Code kann ich jedoch müssen, zu … haben verwenden this->x. Warum?

template <typename T>
class base {

protected:
    int x;
};

template <typename T>
class derived : public base<T> {

public:
    int f() { return this->x; }
};

int main() {
    derived<int> d;
    d.f();
    return 0;
}

  • @Ed Swangren: Entschuldigung, ich habe es beim Posten dieser Frage unter den angebotenen Antworten verpasst. Davor habe ich lange nach der Antwort gesucht.

    – Ali

    10. Januar 2011 um 1:58 Uhr

  • Dies geschieht aufgrund der zweiphasigen Namenssuche (die nicht alle Compiler standardmäßig verwenden) und abhängiger Namen. Es gibt 3 Lösungen für dieses Problem, außer dem Präfix x mit this->nämlich: 1) Verwenden Sie das Präfix base<T>::x, 2) Füge eine Aussage hinzu using base<T>::x, 3) Verwenden Sie einen globalen Compilerschalter, der den zulässigen Modus aktiviert. Die Vor- und Nachteile dieser Lösungen werden unter stackoverflow.com/questions/50321788/… beschrieben.

    – Georg Robinson

    14. Mai 2018 um 13:53 Uhr


Kurze Antwort: um zu machen x ein abhängiger Name, sodass die Suche verzögert wird, bis der Vorlagenparameter bekannt ist.

Lange Antwort: Wenn ein Compiler eine Vorlage sieht, soll er bestimmte Prüfungen sofort durchführen, ohne den Vorlagenparameter zu sehen. Andere werden zurückgestellt, bis der Parameter bekannt ist. Es heißt zweiphasige Kompilierung, und MSVC tut es nicht, aber es wird vom Standard verlangt und von den anderen großen Compilern implementiert. Wenn Sie möchten, muss der Compiler das Template kompilieren, sobald er es sieht (zu einer Art interner Parsing-Tree-Darstellung), und das Kompilieren der Instanziierung auf später verschieben.

Die Überprüfungen, die an der Vorlage selbst durchgeführt werden und nicht an bestimmten Instanziierungen davon, erfordern, dass der Compiler in der Lage ist, die Grammatik des Codes in der Vorlage aufzulösen.

In C++ (und C) müssen Sie manchmal wissen, ob etwas ein Typ ist oder nicht, um die Grammatik des Codes aufzulösen. Zum Beispiel:

#if WANT_POINTER
    typedef int A;
#else
    int A;
#endif
static const int x = 2;
template <typename T> void foo() { A *x = 0; }

Wenn A ein Typ ist, deklariert das einen Zeiger (mit keinem anderen Effekt, als die globale x). Wenn A ein Objekt ist, ist das eine Multiplikation (und wenn ein Operator es nicht überlädt, ist es illegal, einem rvalue zuzuweisen). Wenn es falsch ist, muss dieser Fehler diagnostiziert werden in Phase 1wird es vom Standard als Fehler definiert in der Vorlage, nicht in einer bestimmten Instanziierung davon. Auch wenn die Vorlage nie instanziiert wird, wenn A eine ist int dann ist der obige Code falsch formatiert und muss diagnostiziert werden, genau wie wenn foo war überhaupt keine Vorlage, sondern eine einfache Funktion.

Nun, der Standard sagt, dass Namen welche sind nicht abhängig von Template-Parametern müssen in Phase 1 auflösbar sein. A hier ist kein abhängiger Name, er bezieht sich unabhängig vom Typ auf dasselbe T. Es muss also definiert werden, bevor die Vorlage definiert wird, um in Phase 1 gefunden und überprüft zu werden.

T::A wäre ein Name, der von T abhängt. Wir können unmöglich in Phase 1 wissen, ob das ein Typ ist oder nicht. Der Typ, der letztendlich verwendet wird T in einer Instanziierung ist höchstwahrscheinlich noch nicht einmal definiert, und selbst wenn es so wäre, wissen wir nicht, welche Typen als unser Vorlagenparameter verwendet werden. Aber wir müssen die Grammatik lösen, um unsere wertvollen Phase-1-Prüfungen auf schlecht geformte Vorlagen durchzuführen. Der Standard hat also eine Regel für abhängige Namen – der Compiler muss davon ausgehen, dass es sich nicht um Typen handelt, es sei denn, sie sind mit qualifiziert typename zu spezifizieren, dass sie sind Arten oder in bestimmten eindeutigen Kontexten verwendet werden. Zum Beispiel im template <typename T> struct Foo : T::A {};, T::A wird als Basisklasse verwendet und ist daher eindeutig ein Typ. Wenn Foo wird mit einem Typ instanziiert, der einen Datenmember hat A Anstelle eines verschachtelten Typs A ist dies ein Fehler im Code, der die Instanziierung durchführt (Phase 2), kein Fehler in der Vorlage (Phase 1).

Aber was ist mit einer Klassenvorlage mit einer abhängigen Basisklasse?

template <typename T>
struct Foo : Bar<T> {
    Foo() { A *x = 0; }
};

Ist A ein abhängiger Name oder nicht? Mit Basisklassen, irgendein name könnte in der Basisklasse erscheinen. Wir könnten also sagen, dass A ein abhängiger Name ist, und ihn als Nicht-Typ behandeln. Dies hätte den unerwünschten Effekt, dass jeden Namen in Foo ist abhängig, und daher jede Art verwendet in Foo (außer eingebaute Typen) muss qualifiziert werden. Innerhalb von Foo müssten Sie schreiben:

typename std::string s = "hello, world";

da std::string wäre ein abhängiger Name und wird daher als Nicht-Typ angenommen, sofern nicht anders angegeben. Autsch!

Ein zweites Problem beim Zulassen Ihres bevorzugten Codes (return x;) ist das auch wenn Bar vorher definiert ist Foound x kein Mitglied in dieser Definition ist, könnte jemand später eine Spezialisierung von definieren Bar für irgendeinen Typ Bazso dass Bar<Baz> hat ein Datenelement xund dann instanziieren Foo<Baz>. In dieser Instanziierung würde Ihre Vorlage also das Datenelement zurückgeben, anstatt das globale zurückzugeben x. Oder umgekehrt, wenn die Basisvorlagendefinition von Bar hatte xkönnten sie eine Spezialisierung ohne sie definieren, und Ihre Vorlage würde nach einer globalen suchen x einkehren Foo<Baz>. Ich denke, das wurde als genauso überraschend und beunruhigend beurteilt wie das Problem, das Sie haben, aber es ist so schweigend überraschend, im Gegensatz zu einem überraschenden Fehler.

Um diese Probleme zu vermeiden, besagt der geltende Standard, dass abhängige Basisklassen von Klassenvorlagen nur dann für die Suche berücksichtigt werden, wenn dies ausdrücklich angefordert wird. Dies verhindert, dass alles abhängig ist, nur weil es in einer abhängigen Basis gefunden werden könnte. Es hat auch den unerwünschten Effekt, den Sie sehen – Sie müssen Sachen aus der Basisklasse qualifizieren oder sie werden nicht gefunden. Es gibt drei gängige Arten zu machen A abhängig:

  • using Bar<T>::A; in der Klasse – A bezieht sich jetzt auf etwas in Bar<T>also abhängig.
  • Bar<T>::A *x = 0; am Verwendungsort – Nochmals, A ist definitiv drin Bar<T>. Dies ist eine Multiplikation da typename wurde nicht verwendet, also möglicherweise ein schlechtes Beispiel, aber wir müssen bis zur Instanziierung warten, um herauszufinden, ob operator*(Bar<T>::A, x) gibt einen rvalue zurück. Wer weiß, vielleicht tut es das…
  • this->A; am Einsatzort – A Mitglied ist, also wenn es nicht drin ist Fooes muss in der Basisklasse sein, wieder sagt der Standard, dass dies es abhängig macht.

Die zweiphasige Kompilierung ist fummelig und schwierig und führt einige überraschende Anforderungen für zusätzliche Wortwahl in Ihren Code ein. Aber ähnlich wie die Demokratie ist es wahrscheinlich die schlechteste Art, Dinge zu tun, abgesehen von allen anderen.

Sie könnten vernünftigerweise argumentieren, dass in Ihrem Beispiel return x; macht keinen Sinn, wenn x ist ein verschachtelter Typ in der Basisklasse, also sollte die Sprache (a) sagen, dass es sich um einen abhängigen Namen handelt, und (2) ihn als Nicht-Typ behandeln, und Ihr Code würde ohne funktionieren this->. Bis zu einem gewissen Grad sind Sie das Opfer von Kollateralschäden durch die Lösung eines Problems, das in Ihrem Fall nicht zutrifft, aber es gibt immer noch das Problem, dass Ihre Basisklasse möglicherweise Namen unter Ihnen einführt, die Globals beschatten, oder keine Namen hat, die Sie dachten sie hatten, und stattdessen ein globales Wesen gefunden.

Sie könnten möglicherweise auch argumentieren, dass der Standardwert für abhängige Namen das Gegenteil sein sollte (Typ annehmen, es sei denn, er ist irgendwie als Objekt angegeben) oder dass der Standardwert kontextsensitiver sein sollte (in std::string s = "";, std::string könnte als Typ gelesen werden, da nichts anderes grammatikalisch sinnvoll ist, obwohl std::string *s = 0; ist nicht eindeutig). Auch hier weiß ich nicht genau, wie die Regeln vereinbart wurden. Meine Vermutung ist, dass die Anzahl der erforderlichen Textseiten gegen die Erstellung vieler spezifischer Regeln dafür spricht, welche Kontexte einen Typ und welche einen Nicht-Typ annehmen.

  • Oh, schöne ausführliche Antwort. Klärte ein paar Dinge, die ich nie nachgeschlagen habe. 🙂 +1

    – jalf

    10. Januar 2011 um 3:41 Uhr

  • @jalf: Gibt es so etwas wie C++QTWBFAETYNSYEWTKTAAHMITTBGOW – “Fragen, die häufig gestellt würden, außer dass Sie sich nicht sicher sind, ob Sie die Antwort wissen möchten und wichtigere Dinge zu erledigen haben”?

    – Steve Jessop

    10. Januar 2011 um 3:51 Uhr


  • außergewöhnliche Antwort, frage mich, ob die Frage in die FAQ passen könnte.

    – Matthias M.

    10. Januar 2011 um 8:19 Uhr

  • Whoa, können wir enzyklopädisch sagen? Gib mir fünf Ein subtiler Punkt jedoch: „Wenn Foo mit einem Typ instanziiert wird, der ein Datenelement A anstelle eines verschachtelten Typs A hat, ist das ein Fehler im Code, der die Instanziierung durchführt (Phase 2), kein Fehler in der Vorlage (Phase 1 ).” Vielleicht wäre es besser zu sagen, dass die Vorlage nicht fehlerhaft ist, aber dies könnte immer noch ein Fall einer falschen Annahme oder eines Logikfehlers seitens des Vorlagenautors sein. Wenn die gekennzeichnete Instanziierung tatsächlich der beabsichtigte Anwendungsfall wäre, wäre die Vorlage falsch.

    – Ionoklast Brigham

    12. Juli 2013 um 19:53 Uhr


  • @JohnH. Da mehrere Compiler implementieren -fpermissive oder ähnliches, ja es ist möglich. Ich kenne die Details der Implementierung nicht, aber der Compiler muss die Auflösung aufschieben x bis es die eigentliche Tempate-Basisklasse kennt T. Im Prinzip könnte es also im nicht-permissiven Modus die Tatsache aufzeichnen, dass es verschoben wurde, es verschieben, die Suche durchführen, sobald es erfolgt ist T, und wenn die Suche erfolgreich ist, geben Sie den von Ihnen vorgeschlagenen Text aus. Es wäre ein sehr genauer Vorschlag, wenn er nur in den Fällen gemacht würde, in denen er funktioniert: die Chancen, dass der Benutzer etwas anderes meinte x von noch einem anderen Bereich sind ziemlich winzig!

    – Steve Jessop

    25. Mai 2020 um 22:47 Uhr


Warum muss ich uber den this Zeiger auf Template Basisklassenmitglieder zugreifen
Ali

(Originalantwort vom 10.01.2011)

Ich glaube, ich habe die Antwort gefunden: GCC-Problem: Verwenden eines Mitglieds einer Basisklasse, das von einem Vorlagenargument abhängt. Die Antwort ist nicht spezifisch für gcc.


Aktualisieren: Als Antwort auf Michaels Kommentar, von der Entwurf N3337 des C++11-Standards:

14.6.2 Abhängige Namen [temp.dep]
[…]

3 Wenn bei der Definition einer Klasse oder Klassenvorlage eine Basisklasse von einem Vorlagenparameter abhängt, wird der Gültigkeitsbereich der Basisklasse während der Suche nach nicht qualifizierten Namen weder zum Zeitpunkt der Definition der Klassenvorlage oder des Mitglieds noch während einer Instanziierung von untersucht die Klassenvorlage oder das Mitglied.

Ob „weil die Norm es so vorschreibt“ zählt als Antwort, ich weiß es nicht. Wir können jetzt fragen, warum der Standard dies vorschreibt, aber wie Steve Jessops ausgezeichnete Antwort und andere darauf hinweisen, ist die Antwort auf diese letztere Frage ziemlich lang und fraglich. Leider ist es beim C++-Standard oft fast unmöglich, kurz und in sich geschlossen zu erklären, warum der Standard etwas vorschreibt; dies gilt auch für die letztgenannte Frage.

Die x wird während der Vererbung ausgeblendet. Sie können einblenden über:

template <typename T>
class derived : public base<T> {

public:
    using base<T>::x;             // added "using" statement
    int f() { return x; }
};

  • Diese Antwort erklärt nicht warum es ist versteckt.

    – jamesdlin

    10. Januar 2011 um 2:49 Uhr

  • Ich bekomme base<T> is not a namespace or unscoped enum

    – JDługosz

    29. September 2020 um 15:31 Uhr

1002520cookie-checkWarum muss ich über den this-Zeiger auf Template-Basisklassenmitglieder zugreifen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy