Während ich den Quellcode von Qt untersuche, habe ich gesehen, dass Trolltech-Jungs ihn ausdrücklich verwenden this
Schlüsselwort für den Zugriff auf ein Feld im Destruktor.
inline ~QScopedPointer()
{
T *oldD = this->d;
Cleanup::cleanup(oldD);
this->d = 0;
}
Also, was ist der Sinn dieser Verwendung? Gibt es Vorteile?
Bearbeiten: Für diejenigen, die dafür stimmen, diese Frage zu schließen, vermute ich, dass diese Verwendung für einige Klassenvererbungsfälle gilt
Ein Teil von QScopedPointer-Klasse Definition:
template <typename T, typename Cleanup = QScopedPointerDeleter<T> >
class QScopedPointer
C++-Antwort (allgemeine Antwort)
Betrachten Sie eine Vorlagenklasse Derived
mit einer Template-Basisklasse:
template <typename T>
class Base {
public:
int d;
};
template <typename T>
class Derived : public Base<T> {
void f () {
this->d = 0;
}
};
this
Typ hat Derived<T>
ein Typ, der davon abhängt T
. Damit this
hat einen abhängigen Typ. Damit this->d
macht d
ein abhängiger Name. Abhängige Namen werden im Kontext der Vorlagendefinition als nicht abhängige Namen und im Kontext der Instanziierung nachgeschlagen.
Ohne this->
der Name d
würde nur als nicht abhängiger Name nachgeschlagen und nicht gefunden werden.
Eine andere Lösung ist zu deklarieren d
in der Vorlagendefinition selbst:
template <typename T>
class Derived : public Base<T> {
using Base::d;
void f () {
d = 0;
}
};
QAntwort (spezifische Antwort)
d
ist ein Mitglied von QScopedPointer
. Es ist kein geerbtes Mitglied. this->
ist hier nicht nötig.
OTOH, QScopedArrayPointer
ist eine Vorlagenklasse und d
ist ein geerbtes Mitglied einer Template-Basisklasse:
template <typename T, typename Cleanup = QScopedPointerArrayDeleter<T> >
class QScopedArrayPointer : public QScopedPointer<T, Cleanup>
damit this->
ist notwendig Hier:
inline T &operator[](int i)
{
return this->d[i];
}
Es ist leicht zu sehen, dass es einfacher ist, einfach zu setzen this->
überall, überallhin, allerorts.
Verstehen Sie den Grund
Ich denke, es ist nicht allen C++-Benutzern klar, warum Namen in nicht abhängigen Basisklassen, aber nicht in abhängigen Basisklassen nachgeschlagen werden:
class Base0 {
public:
int nd;
};
template <typename T>
class Derived2 :
public Base0, // non-dependent base
public Base<T> { // dependent base
void f () {
nd; // Base0::b
d; // lookup of "d" finds nothing
f (this); // lookup of "f" finds nothing
// will find "f" later
}
};
Neben “der Standard sagt es” gibt es einen Grund: Ursache für das Binden von Wegnamen in Vorlagen funktioniert.
Vorlagen können Namen haben, die spät gebunden werden, wenn die Vorlage instanziiert wird: zum Beispiel f
in f (this)
. An der Stelle von Derived2::f()
Definition gibt es keinen Variablen-, Funktions- oder Typnamen f
dem Compiler bekannt. Die Menge bekannter Entitäten, die f
beziehen könnte, ist an dieser Stelle leer. Dies ist kein Problem, da der Compiler weiß, dass er suchen wird f
später als Funktionsname oder als Vorlagenfunktionsname.
OTOH, der Compiler weiß nicht, was er damit anfangen soll d
; es ist kein (aufgerufener) Funktionsname. Es gibt keine Möglichkeit, eine späte Bindung an Namen von nicht (aufgerufenen) Funktionen durchzuführen.
Nun, all dies mag wie elementares Wissen über die Polymorphie von Vorlagen zur Kompilierzeit erscheinen. Die eigentliche Frage scheint zu sein: Warum nicht d
gebunden an Base<T>::d
zum Zeitpunkt der Vorlagendefinition?
Das eigentliche Problem ist, dass es keine gibt Base<T>::d
zum Zeitpunkt der Vorlagendefinition, weil Es gibt keinen vollständigen Typ Base<T>
zu dieser Zeit: Base<T>
ist deklariert, aber nicht definiert! Sie fragen sich vielleicht: Was ist damit:
template <typename T>
class Base {
public:
int d;
};
es sieht aus wie die Definition eines vollständigen Typs!
Eigentlich sieht es bis zur Instanziierung eher so aus:
template <typename T>
class Base;
zum Compiler. Ein Name kann nicht in einer Klassenvorlage nachgeschlagen werden! Aber nur in einer Template-Spezialisierung (Instanziierung). Die Vorlage ist eine Fabrik, um eine Vorlagenspezialisierung vorzunehmen, Eine Vorlage ist kein Satz von Vorlagenspezialisierungen. Der Compiler kann suchen d
in Base<T>
für irgendeinen bestimmten Typ T
aber es kann nicht gesucht werden d
in der Klassenvorlage Base
. Bis zu einer Art T
festgestellt wird, Base<T>::d
bleibt das Abstrakte Base<T>::d
; nur beim Typ T
ist bekannt, Base<T>::d
beginnen, auf eine Variable vom Typ zu verweisen int
.
Die Folge davon ist, dass die Klasse Vorlage Derived2
hat eine vollständige Basisklasse Base0
aber eine unvollständige (forward deklarierte) Basisklasse Base
. Nur für einen bekannten Typ T
die “Vorlagenklasse” (Spezialisierungen einer Klassenvorlage) Derived2<T>
hat eine vollständige Basisklasse, genau wie jede normale Klasse.
Das siehst du jetzt:
template <typename T>
class Derived : public Base<T>
ist eigentlich ein Basisklassen-Spezifikationsvorlage (eine Fabrik zum Erstellen von Basisklassenspezifikationen), die anderen Regeln folgt als eine Basisklassenspezifikation innerhalb einer Vorlage.
Anmerkung: Dem Leser ist vielleicht aufgefallen, dass ich am Ende der Erklärung ein paar Sätze erfunden habe.
Das ist ganz anders: hier d
ist ein qualifizierter Name in Derived<T>
und Derived<T>
ist seitdem abhängig T
ist ein Vorlagenparameter. Ein qualifizierter Name kann spät gebunden werden, auch wenn es sich nicht um einen (aufgerufenen) Funktionsnamen handelt.
Noch eine andere Lösung ist:
template <typename T>
class Derived : public Base<T> {
void f () {
Derived::d = 0; // qualified name
}
};
Dies ist gleichwertig.
Wenn Sie denken, dass innerhalb der Definition von Derived<T>
die Behandlung von Derived<T>
manchmal als bekannte vollständige Klasse und manchmal als unbekannte Klasse in widersprüchlicher Weise, nun, Sie haben Recht.
Ich würde vermuten, dass es sich um eine überladene Verwendung der Cleanup() -Routine handelt. Der übergebene Typ wird explizit durch den Vorlagentyp T gesteuert, der wiederum steuern kann, welche überladene Version von Cleanup() aufgerufen wird.