C++
Der This Pointer
Suche…
Bemerkungen
this
Zeiger ist ein Schlüsselwort für C ++, daher ist keine Bibliothek erforderlich, um dies zu implementieren. Und vergiss nicht, this
ist ein Zeiger! Du kannst also nicht:
this.someMember();
Wenn Sie mit dem Pfeilsymbol auf Elementfunktionen oder Elementvariablen zugreifen, verwenden Sie das Pfeilsymbol ->
:
this->someMember();
Weitere hilfreiche Links zum besseren Verständnis this
Zeigers:
http://www.geeksforgeeks.org/this-pointer-in-c/
https://www.tutorialspoint.com/cplusplus/cpp_this_pointer.htm
dieser Zeiger
Alle nicht statischen Memberfunktionen haben einen ausgeblendeten Parameter, einen Zeiger auf eine Instanz der Klasse mit dem Namen this
. Dieser Parameter wird am Anfang der Parameterliste automatisch eingefügt und vollständig vom Compiler behandelt. Wenn innerhalb einer Member-Funktion auf ein Member der Klasse zugegriffen wird, wird durch this
Funktion im Hintergrund darauf zugegriffen. Dadurch kann der Compiler eine einzige nicht statische Memberfunktion für alle Instanzen verwenden, und eine Memberfunktion kann andere Memberfunktionen polymorph aufrufen.
struct ThisPointer {
int i;
ThisPointer(int ii);
virtual void func();
int get_i() const;
void set_i(int ii);
};
ThisPointer::ThisPointer(int ii) : i(ii) {}
// Compiler rewrites as:
ThisPointer::ThisPointer(int ii) : this->i(ii) {}
// Constructor is responsible for turning allocated memory into 'this'.
// As the constructor is responsible for creating the object, 'this' will not be "fully"
// valid until the instance is fully constructed.
/* virtual */ void ThisPointer::func() {
if (some_external_condition) {
set_i(182);
} else {
i = 218;
}
}
// Compiler rewrites as:
/* virtual */ void ThisPointer::func(ThisPointer* this) {
if (some_external_condition) {
this->set_i(182);
} else {
this->i = 218;
}
}
int ThisPointer::get_i() const { return i; }
// Compiler rewrites as:
int ThisPointer::get_i(const ThisPointer* this) { return this->i; }
void ThisPointer::set_i(int ii) { i = ii; }
// Compiler rewrites as:
void ThisPointer::set_i(ThisPointer* this, int ii) { this->i = ii; }
In einem Konstruktor kann this
sicher verwendet werden, um (implizit oder explizit) auf ein Feld zuzugreifen, das bereits initialisiert wurde, oder auf ein Feld in einer übergeordneten Klasse. umgekehrt ist (implizit oder explizit) der Zugriff auf Felder, die noch nicht initialisiert wurden, oder auf Felder in einer abgeleiteten Klasse, unsicher (da die abgeleitete Klasse noch nicht erstellt wurde und ihre Felder daher weder initialisiert noch vorhanden sind). Es ist auch nicht sicher virtuelle Mitgliederfunktionen bis hin zu nennen this
im Konstruktor, wie alle abgeleiteten Klasse Funktionen werden nicht berücksichtigt (aufgrund der abgeleiteten Klasse noch nicht aufgebaut ist, und somit der Konstruktor noch nicht die V - Tabelle zu aktualisieren).
Beachten Sie außerdem, dass der Typ des Objekts in einem Konstruktor der Typ ist, den dieser Konstruktor erstellt. Dies gilt auch dann, wenn das Objekt als abgeleiteter Typ deklariert ist. In dem folgenden Beispiel sind ctd_good
und ctd_bad
beispielsweise CtorThisBase
in CtorThisBase()
und CtorThis
in CtorThis()
, obwohl der kanonische Typ CtorThisDerived
. Da die stärker abgeleiteten Klassen um die Basisklasse herum aufgebaut werden, durchläuft die Instanz nach und nach die Klassenhierarchie, bis es sich um eine vollständig konstruierte Instanz des beabsichtigten Typs handelt.
class CtorThisBase {
short s;
public:
CtorThisBase() : s(516) {}
};
class CtorThis : public CtorThisBase {
int i, j, k;
public:
// Good constructor.
CtorThis() : i(s + 42), j(this->i), k(j) {}
// Bad constructor.
CtorThis(int ii) : i(ii), j(this->k), k(b ? 51 : -51) {
virt_func();
}
virtual void virt_func() { i += 2; }
};
class CtorThisDerived : public CtorThis {
bool b;
public:
CtorThisDerived() : b(true) {}
CtorThisDerived(int ii) : CtorThis(ii), b(false) {}
void virt_func() override { k += (2 * i); }
};
// ...
CtorThisDerived ctd_good;
CtorThisDerived ctd_bad(3);
Mit diesen Klassen und Memberfunktionen:
- Im guten Konstruktor für
ctd_good
:-
CtorThisBase
ist vollständig erstellt, wenn derCtorThis
Konstruktor eingegeben wird. Daher befindet sichs
während der Initialisierung voni
in einem gültigen Zustand und kann somit aufgerufen werden. -
i
wird initialisiert, bevorj(this->i)
erreicht ist. Daher befindet sichi
während der Initialisierung vonj
in einem gültigen Zustand und kann somit darauf zugegriffen werden. -
j
wird initialisiert, bevork(j)
erreicht wird. Daher befindet sichj
während der Initialisierung vonk
in einem gültigen Zustand und kann somit darauf zugegriffen werden.
-
- Im schlechten Konstruktor für
ctd_bad
:-
k
wird initialisiert, nachdemj(this->k)
erreicht ist. Daher istk
während der Initialisierung vonj
in einem ungültigen Zustand, und der Zugriff darauf verursacht ein undefiniertes Verhalten. -
CtorThisDerived
wird erst nachCtorThis
. Daher istb
während der Initialisierung vonk
in einem ungültigen Zustand, und der Zugriff darauf verursacht ein undefiniertes Verhalten. - Das Objekt
ctd_bad
ist noch einCtorThis
bis esCtorThis()
verlässt, und wird nicht aktualisiert, um dieCtorThisDerived
CtorThisDerived bisCtorThisDerived()
. Dahervirt_func()
CtorThis::virt_func()
, unabhängig davon, ob dies oderCtorThisDerived::virt_func()
.
-
Verwenden dieses Zeigers für den Zugriff auf Mitgliedsdaten
In diesem Zusammenhang ist die Verwendung des Zeigers ' this
nicht unbedingt erforderlich, macht den Code jedoch für den Leser klarer, indem er anzeigt, dass eine bestimmte Funktion oder Variable eine Klasse ist. Ein Beispiel in dieser Situation:
// Example for this pointer
#include <iostream>
#include <string>
using std::cout;
using std::endl;
class Class
{
public:
Class();
~Class();
int getPrivateNumber () const;
private:
int private_number = 42;
};
Class::Class(){}
Class::~Class(){}
int Class::getPrivateNumber() const
{
return this->private_number;
}
int main()
{
Class class_example;
cout << class_example.getPrivateNumber() << endl;
}
Sehen sie in Aktion hier .
Verwenden dieses Zeigers zur Unterscheidung zwischen Mitgliedsdaten und Parametern
Dies ist eine nützliche Strategie, um Elementdaten von Parametern zu unterscheiden. Nehmen wir folgendes Beispiel:
// Dog Class Example
#include <iostream>
#include <string>
using std::cout;
using std::endl;
/*
* @class Dog
* @member name
* Dog's name
* @function bark
* Dog Barks!
* @function getName
* To Get Private
* Name Variable
*/
class Dog
{
public:
Dog(std::string name);
~Dog();
void bark() const;
std::string getName() const;
private:
std::string name;
};
Dog::Dog(std::string name)
{
/*
* this->name is the
* name variable from
* the class dog . and
* name is from the
* parameter of the function
*/
this->name = name;
}
Dog::~Dog(){}
void Dog::bark() const
{
cout << "BARK" << endl;
}
std::string Dog::getName() const
{
return this->name;
}
int main()
{
Dog dog("Max");
cout << dog.getName() << endl;
dog.bark();
}
Sie können hier im Konstruktor sehen, dass wir Folgendes ausführen:
this->name = name;
Hier sehen Sie, dass wir den Parameternamen dem Namen der privaten Variablen der Klasse Dog (this-> name) zuordnen.
So sehen Sie die Ausgabe des obigen Codes: http://cpp.sh/75r7
diese Pointer CV-Qualifiers
this
kann auch cv-qualifiziert sein, genauso wie jeder andere Zeiger. Da this
Parameter jedoch nicht in der Parameterliste aufgeführt ist, ist hierfür eine spezielle Syntax erforderlich. Die CV-Qualifikationsmerkmale werden nach der Parameterliste aufgeführt, jedoch vor dem Hauptteil der Funktion.
struct ThisCVQ {
void no_qualifier() {} // "this" is: ThisCVQ*
void c_qualifier() const {} // "this" is: const ThisCVQ*
void v_qualifier() volatile {} // "this" is: volatile ThisCVQ*
void cv_qualifier() const volatile {} // "this" is: const volatile ThisCVQ*
};
Da this
um einen Parameter handelt, kann eine Funktion auf der Grundlage this
cv-Qualifier (s) überladen werden .
struct CVOverload {
int func() { return 3; }
int func() const { return 33; }
int func() volatile { return 333; }
int func() const volatile { return 3333; }
};
Wenn this
const
(einschließlich const volatile
), kann die Funktion weder implizit noch explizit in Member-Variablen schreiben. Die einzige Ausnahme hiervon sind mutable
Membervariablen , die unabhängig von der Konstante geschrieben werden können. Aus diesem Grund wird mit const
angezeigt, dass die Member-Funktion den logischen Zustand des Objekts (die Art, wie das Objekt der Außenwelt erscheint) nicht ändert, selbst wenn es den physischen Zustand (die Art, wie das Objekt unter der Haube aussieht) ändert ).
Der logische Zustand ist die Art und Weise, wie das Objekt außerhalb der Beobachter erscheint. Es ist nicht direkt an den physischen Zustand gebunden und wird möglicherweise nicht einmal als physischer Zustand gespeichert. Solange externe Beobachter keine Änderungen sehen können, ist der logische Zustand konstant, selbst wenn Sie jedes einzelne Bit im Objekt umdrehen.
Der physikalische Zustand, auch als bitweiser Zustand bezeichnet, ist, wie das Objekt im Speicher abgelegt wird. Dies ist das Kleinste des Objekts, die rohen Einsen und Nullen, aus denen seine Daten bestehen. Ein Objekt ist nur physisch konstant, wenn sich seine Darstellung im Speicher niemals ändert.
Beachten Sie, dass C ++ Basen const
auf logischen Zustand ness, nicht physischen Zustand.
class DoSomethingComplexAndOrExpensive {
mutable ResultType cached_result;
mutable bool state_changed;
ResultType calculate_result();
void modify_somehow(const Param& p);
// ...
public:
DoSomethingComplexAndOrExpensive(Param p) : state_changed(true) {
modify_somehow(p);
}
void change_state(Param p) {
modify_somehow(p);
state_changed = true;
}
// Return some complex and/or expensive-to-calculate result.
// As this has no reason to modify logical state, it is marked as "const".
ResultType get_result() const;
};
ResultType DoSomethingComplexAndOrExpensive::get_result() const {
// cached_result and state_changed can be modified, even with a const "this" pointer.
// Even though the function doesn't modify logical state, it does modify physical state
// by caching the result, so it doesn't need to be recalculated every time the function
// is called. This is indicated by cached_result and state_changed being mutable.
if (state_changed) {
cached_result = calculate_result();
state_changed = false;
}
return cached_result;
}
Beachten Sie, dass Sie technisch nutzen könnten const_cast
auf this
, um es nicht-cv-qualifiziert zu machen, sollten Sie wirklich, wirklich nicht, und sollte verwenden mutable
statt. Ein const_cast
haftet nicht definiertes Verhalten aufzurufen , wenn auf ein Objekt verwendet , die tatsächlich ist const
, während mutable
ausgelegt ist , um sicher zu sein zu verwenden. Es ist jedoch möglich, dass Sie in sehr altem Code darauf stoßen.
class CVAccessor {
int arr[5];
public:
const int& get_arr_element(size_t i) const { return arr[i]; }
int& get_arr_element(size_t i) {
return const_cast<int&>(const_cast<const CVAccessor*>(this)->get_arr_element(i));
}
};
Dies verhindert unnötige Duplizierungen von Code.
Wenn this
volatile
(einschließlich const volatile
), wird es wie bei normalen Zeigern bei jedem Zugriff aus dem Speicher geladen, anstatt zwischengespeichert zu werden. Dies hat die gleichen Auswirkungen auf die Optimierung wie die Angabe eines anderen Zeigers als volatile
, weshalb Vorsicht geboten ist.
Beachten Sie, dass , wenn eine Instanz cv-qualifiziert ist, ist es die einzige Mitglied Funktionen zugreifen , sind Funktionen , dessen Mitglieds ist erlaubt this
Zeiger ist mindestens so cv-qualifiziert als Instanz selbst:
- Nicht-CV-Instanzen können auf alle Member-Funktionen zugreifen.
-
const
Instanzen können aufconst volatile
Funktionen vonconst
undconst volatile
zugreifen. -
volatile
Instanzen können aufvolatile
undconst volatile
Funktionen zugreifen. -
const volatile
Instanzen können aufconst volatile
Funktionen zugreifen.
Dies ist einer der wichtigsten Grundsätze der const
Korrektheit .
struct CVAccess {
void func() {}
void func_c() const {}
void func_v() volatile {}
void func_cv() const volatile {}
};
CVAccess cva;
cva.func(); // Good.
cva.func_c(); // Good.
cva.func_v(); // Good.
cva.func_cv(); // Good.
const CVAccess c_cva;
c_cva.func(); // Error.
c_cva.func_c(); // Good.
c_cva.func_v(); // Error.
c_cva.func_cv(); // Good.
volatile CVAccess v_cva;
v_cva.func(); // Error.
v_cva.func_c(); // Error.
v_cva.func_v(); // Good.
v_cva.func_cv(); // Good.
const volatile CVAccess cv_cva;
cv_cva.func(); // Error.
cv_cva.func_c(); // Error.
cv_cva.func_v(); // Error.
cv_cva.func_cv(); // Good.
diese Pointer Ref-Qualifiers
Ähnlich wie bei this
CV-Qualifiers können wir auch Ref-Qualifier auf *this
anwenden. Ref-Qualifizierer verwendet , um zwischen normal und rvalue Referenz Semantik zu wählen, so dass der Compiler entweder Kopie verwenden oder Semantik je nachdem , welche besser geeignet bewegen, und werden angewandt *this
anstelle von this
.
Beachten Sie, dass trotz Ref-Qualifiers, die die Referenzsyntax verwenden, this
selbst immer noch ein Zeiger ist. Beachten Sie auch, dass ref-Qualifier den Typ von *this
nicht wirklich ändern. Es ist nur einfacher, ihre Auswirkungen zu beschreiben und zu verstehen, indem man sie so betrachtet, als ob sie es taten.
struct RefQualifiers {
std::string s;
RefQualifiers(const std::string& ss = "The nameless one.") : s(ss) {}
// Normal version.
void func() & { std::cout << "Accessed on normal instance " << s << std::endl; }
// Rvalue version.
void func() && { std::cout << "Accessed on temporary instance " << s << std::endl; }
const std::string& still_a_pointer() & { return this->s; }
const std::string& still_a_pointer() && { this->s = "Bob"; return this->s; }
};
// ...
RefQualifiers rf("Fred");
rf.func(); // Output: Accessed on normal instance Fred
RefQualifiers{}.func(); // Output: Accessed on temporary instance The nameless one
Eine Member-Funktion kann weder mit als auch ohne Ref-Qualifier Überladungen haben. Der Programmierer muss zwischen dem einen oder dem anderen wählen. Zum Glück können cv-qualifiers in Verbindung mit ref-qualifiers verwendet werden, so dass die const
Richtigkeitsregeln befolgt werden können.
struct RefCV {
void func() & {}
void func() && {}
void func() const& {}
void func() const&& {}
void func() volatile& {}
void func() volatile&& {}
void func() const volatile& {}
void func() const volatile&& {}
};