C++
The This Pointer
Ricerca…
Osservazioni
this
puntatore è una parola chiave per C ++ quindi non è necessaria alcuna libreria per implementarlo. E non dimenticare che this
è un puntatore! Quindi non puoi fare:
this.someMember();
Mentre accedi alle funzioni membro o alle variabili membro dai puntatori usando il simbolo freccia ->
:
this->someMember();
Altri link utili per una migliore comprensione di this
puntatore:
http://www.geeksforgeeks.org/this-pointer-in-c/
https://www.tutorialspoint.com/cplusplus/cpp_this_pointer.htm
questo puntatore
Tutte le funzioni membro non statiche hanno un parametro nascosto, un puntatore a un'istanza della classe, chiamato this
; questo parametro viene inserito silenziosamente all'inizio dell'elenco dei parametri e gestito interamente dal compilatore. Quando un membro della classe si accede all'interno di una funzione membro, viene silenziosamente accede attraverso this
; ciò consente al compilatore di utilizzare un'unica funzione membro non statica per tutte le istanze e consente a una funzione membro di chiamare polimorficamente altre funzioni membro.
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 un costruttore, this
può essere tranquillamente usato per accedere (implicitamente o esplicitamente) a qualsiasi campo che è già stato inizializzato, o qualsiasi campo in una classe genitore; viceversa, (implicitamente o esplicitamente) l'accesso a tutti i campi che non sono ancora stati inizializzati, o qualsiasi campo in una classe derivata, non è sicuro (a causa della classe derivata non ancora costruita, e quindi i suoi campi non vengono né inizializzati né esistenti). È anche pericoloso chiamare le funzioni dei membri virtuali attraverso this
nel costruttore, poiché qualsiasi funzione di classe derivata non sarà considerata (a causa della classe derivata che non è ancora stata costruita, e quindi il suo costruttore non ha ancora aggiornato il vtable).
Si noti inoltre che mentre in un costruttore, il tipo dell'oggetto è il tipo che il costruttore costruisce. Ciò vale anche se l'oggetto è dichiarato come un tipo derivato. Ad esempio, nell'esempio seguente, ctd_good
e ctd_bad
sono di tipo CtorThisBase
all'interno di CtorThisBase()
e digitano CtorThis
all'interno di CtorThis()
, anche se il loro tipo canonico è CtorThisDerived
. Poiché le classi più derivate sono costruite attorno alla classe base, l'istanza passa gradualmente attraverso la gerarchia delle classi fino a quando non è un'istanza completamente costruita del tipo desiderato.
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);
Con queste classi e funzioni membro:
- Nel buon costruttore, per
ctd_good
:-
CtorThisBase
è completamente costruito dal momento inCtorThis
viene inserito il costruttoreCtorThis
. Pertanto,s
è in uno stato valido durante l'inizializzazione dii
, e può quindi essere accessibile. -
i
viene inizializzato prima chej(this->i)
venga raggiunto. Pertanto,i
è in uno stato valido durante l'inizializzazione dij
e può quindi essere accessibile. -
j
viene inizializzato prima chek(j)
venga raggiunto. Pertanto,j
è in uno stato valido durante l'inizializzazione dik
e può quindi essere accessibile.
-
- Nel costruttore
ctd_bad
, perctd_bad
:-
k
viene inizializzato dopo chej(this->k)
viene raggiunto. Pertanto,k
è in uno stato non valido durante l'inizializzazione dij
, e accedervi causa un comportamento indefinito. -
CtorThisDerived
non vieneCtorThisDerived
fino a dopo laCtorThis
. Pertanto,b
è in uno stato non valido durante l'inizializzazione dik
, e accedervi causa un comportamento indefinito. - L'oggetto
ctd_bad
è ancora unCtorThis
fino a quando non lasciaCtorThis()
, e non sarà aggiornato per utilizzareCtorThisDerived
vtable 's finoCtorThisDerived()
. Pertanto,virt_func()
chiameràCtorThis::virt_func()
, indipendentemente dal fatto che sia destinato a chiamarlo oCtorThisDerived::virt_func()
.
-
Usando questo puntatore per accedere ai dati dei membri
In questo contesto, l'utilizzo di this
puntatore non è del tutto necessario, ma renderà il codice più chiaro al lettore, indicando che una determinata funzione o variabile è un membro della classe. Un esempio in questa situazione:
// 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;
}
Guardalo in azione qui .
Usando questo puntatore per distinguere tra dati e parametri dei membri
Questa è una strategia utile per differenziare i dati dei membri dai parametri ... Prendiamo questo esempio:
// 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();
}
Puoi vedere qui nel costruttore che eseguiamo quanto segue:
this->name = name;
Qui, puoi vedere che stiamo assingendo il nome del parametro al nome della variabile privata dalla classe Dog (this-> name).
Per vedere l'output del codice precedente: http://cpp.sh/75r7
questo Pointer CV-qualificatori
this
può anche essere qualificato cv, lo stesso di qualsiasi altro puntatore. Tuttavia, poiché this
parametro non è elencato nell'elenco dei parametri, è necessaria una sintassi speciale; i qualificatori di cv sono elencati dopo la lista dei parametri, ma prima del corpo della funzione.
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*
};
Poiché this
tratta di un parametro, è possibile sovraccaricare una funzione in base a this
cv-qualificatore / i .
struct CVOverload {
int func() { return 3; }
int func() const { return 33; }
int func() volatile { return 333; }
int func() const volatile { return 3333; }
};
Quando this
è const
(includendo const volatile
), la funzione non è in grado di scrivere attraverso le variabili membro, sia implicitamente che esplicitamente. L'unica eccezione è rappresentata dalle variabili dei membri mutable
, che possono essere scritte indipendentemente dalla costanza. A causa di ciò, const
viene utilizzato per indicare che la funzione membro non modifica lo stato logico dell'oggetto (il modo in cui l'oggetto appare al mondo esterno), anche se modifica lo stato fisico (il modo in cui l'oggetto appare sotto la cappa ).
Lo stato logico è il modo in cui l'oggetto appare agli osservatori esterni. Non è direttamente legato allo stato fisico, anzi, potrebbe non essere nemmeno memorizzato come stato fisico. Finché gli osservatori esterni non possono vedere alcuna modifica, lo stato logico è costante, anche se si capovolge ogni singolo bit nell'oggetto.
Lo stato fisico, noto anche come stato bit per bit, è il modo in cui l'oggetto viene archiviato in memoria. Questo è il nitty-gritty dell'oggetto, i raw 1 e 0 che compongono i suoi dati. Un oggetto è fisicamente costante solo se la sua rappresentazione in memoria non cambia mai.
Si noti che le basi C ++ const
ness sullo stato logico, non lo stato fisico.
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;
}
Nota che mentre tecnicamente potresti usare const_cast
su this
per renderlo non-cv-qualificato, in realtà, DAVVERO , non dovresti, e dovresti usare invece il mutable
. Un const_cast
è suscettibile di invocare un comportamento indefinito quando viene utilizzato su un oggetto che è effettivamente const
, mentre mutable
è progettato per essere sicuro da usare. Tuttavia, è possibile che si possa imbattersi in questo codice estremamente vecchio.
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));
}
};
Questo previene inutili duplicazioni di codice.
Come con i puntatori regolari, se this
è volatile
(incluso const volatile
), viene caricato dalla memoria ogni volta che si accede, invece di essere memorizzato nella cache. Questo ha gli stessi effetti sull'ottimizzazione come dichiarare qualsiasi altro puntatore volatile
, quindi bisogna fare attenzione.
Si noti che se un'istanza è cv qualificato, le uniche funzioni membro è autorizzato ad accedere sono funzioni membro cui this
puntatore è almeno altrettanto cv qualificato come istanza stessa:
- Le istanze non cv possono accedere a qualsiasi funzione membro.
-
const
istanzeconst
possono accedere a funzioniconst
econst volatile
. -
volatile
istanzevolatile
possono accedere a funzionivolatile
econst volatile
. -
const volatile
istanzeconst volatile
possono accedere a funzioniconst volatile
.
Questo è uno dei principi chiave della correttezza const
.
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.
questo Pointer Ref-Qualificatori
Analogamente a this
CV-qualificazioni, possiamo applicare anche ref-qualificazioni a *this
. I qualificatori Ref vengono utilizzati per scegliere tra semantica di riferimento normale e valore di riferimento, consentendo al compilatore di utilizzare la copia o spostare la semantica a seconda di quali sono più appropriati e vengono applicati a *this
invece di this
.
Si noti che, nonostante i qualificatori di riferimento che usano la sintassi di riferimento, this
stesso è ancora un puntatore. Si noti inoltre che i qualificatori di ref non cambiano effettivamente il tipo di *this
; è solo più facile descrivere e capire i loro effetti guardandoli come se lo avessero fatto.
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
Una funzione membro non può avere sovraccarichi sia con che senza qualificatori di ref; il programmatore deve scegliere tra l'uno o l'altro. Fortunatamente, i qualificatori di cv possono essere usati insieme ai qualificatori di ref, permettendo di seguire le regole di correttezza delle const
.
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&& {}
};