C++
El puntero este
Buscar..
Observaciones
this
puntero es una palabra clave para C ++, por lo que no se necesita una biblioteca para implementar esto. Y no olvides que this
es un puntero! Así que no puedes hacer:
this.someMember();
A medida que accede a las funciones o variables de los miembros desde los punteros, utilice el símbolo de flecha ->
:
this->someMember();
Otros enlaces útiles para una mejor comprensión de this
puntero:
http://www.geeksforgeeks.org/this-pointer-in-c/
https://www.tutorialspoint.com/cplusplus/cpp_this_pointer.htm
este puntero
Todas las funciones miembro no estáticas tienen un parámetro oculto, un puntero a una instancia de la clase, llamado this
; este parámetro se inserta silenciosamente al principio de la lista de parámetros, y se maneja por completo por el compilador. Cuando se accede a un miembro de la clase dentro de una función miembro, se accede de forma silenciosa a través de this
; esto permite que el compilador utilice una única función miembro no estática para todas las instancias, y permite que una función miembro llame a otras funciones miembro de forma polimórfica.
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; }
En un constructor, this
se puede usar de manera segura para (implícita o explícitamente) acceder a cualquier campo que ya se haya inicializado, o cualquier campo en una clase principal; a la inversa, el acceso (implícito o explícito) a cualquier campo que aún no se haya inicializado, o cualquier campo en una clase derivada, es inseguro (debido a que la clase derivada aún no se ha construido y, por lo tanto, sus campos no están inicializados ni existen). Tampoco es seguro llamar a las funciones miembro virtuales a través de this
en el constructor, ya que no se considerarán las funciones de clase derivadas (debido a que la clase derivada aún no se ha construido y, por lo tanto, su constructor aún no actualiza el vtable).
También tenga en cuenta que, mientras se encuentra en un constructor, el tipo de objeto es el tipo que ese constructor construye. Esto es cierto incluso si el objeto se declara como un tipo derivado. Por ejemplo, en el siguiente ejemplo, ctd_good
y ctd_bad
son tipo CtorThisBase
dentro de CtorThisBase()
, y tipo CtorThis
dentro de CtorThis()
, aunque su tipo canónico es CtorThisDerived
. A medida que las clases más derivadas se construyen alrededor de la clase base, la instancia pasa gradualmente a través de la jerarquía de clases hasta que se trata de una instancia completamente construida de su tipo deseado.
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 estas clases y funciones miembro:
- En el buen constructor, para
ctd_good
:-
CtorThisBase
se construye completamente cuando seCtorThis
constructorCtorThis
. Por lo tanto,s
encuentra en un estado válido al inicializari
y, por lo tanto, se puede acceder a él. -
i
se inicializa antes de que se alcancej(this->i)
. Por lo tanto,i
está en un estado válido al inicializarj
, y por lo tanto se puede acceder a él. -
j
se inicializa antes de alcanzark(j)
. Por lo tanto,j
encuentra en un estado válido al inicializark
, y por lo tanto se puede acceder a él.
-
- En el constructor malo, para
ctd_bad
:-
k
se inicializa después de alcanzarj(this->k)
. Por lo tanto,k
está en un estado no válido al inicializarj
, y acceder a él causa un comportamiento indefinido. -
CtorThisDerived
no se construye hasta después de queCtorThis
se construya. Por lo tanto,b
encuentra en un estado no válido al inicializark
, y acceder a él provoca un comportamiento indefinido. - El objeto
ctd_bad
sigue siendo unCtorThis
hasta que saleCtorThis()
, y no se actualiza para utilizarCtorThisDerived
vtable 's hastaCtorThisDerived()
. Por lo tanto,virt_func()
llamará aCtorThis::virt_func()
, independientemente de si está destinado a llamar a eso oCtorThisDerived::virt_func()
.
-
Uso de este puntero para acceder a datos de miembros
En este contexto, el uso de this
puntero no es completamente necesario, pero hará que su código sea más claro para el lector, al indicar que una función o variable determinada es un miembro de la clase. Un ejemplo en esta situación:
// 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;
}
Véalo en acción aquí .
Uso de este puntero para diferenciar entre datos de miembros y parámetros
Esta es una estrategia realmente útil para diferenciar los datos de los miembros de los parámetros ... Tomemos este ejemplo:
// 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();
}
Puedes ver aquí en el constructor ejecutamos lo siguiente:
this->name = name;
Aquí puede ver que estamos asignando el nombre del parámetro al nombre de la variable privada de la clase Dog (this-> name).
Para ver la salida del código anterior: http://cpp.sh/75r7
este puntero CV-calificadores
this
también puede ser calificado como CV, al igual que cualquier otro puntero. Sin embargo, debido a que this
parámetro no aparece en la lista de parámetros, se requiere una sintaxis especial para esto; los calificadores cv se enumeran después de la lista de parámetros, pero antes del cuerpo de la función.
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*
};
Como this
es un parámetro, una función puede ser sobrecargado basado en su this
cv-calificador (s) .
struct CVOverload {
int func() { return 3; }
int func() const { return 33; }
int func() volatile { return 333; }
int func() const volatile { return 3333; }
};
Cuando this
es const
(incluyendo const volatile
), la función no puede escribir en las variables miembro a través de él, ya sea de forma implícita o explícita. La única excepción a esto son las variables miembro mutable
, que pueden escribirse independientemente de la constancia. Debido a esto, const
se utiliza para indicar que la función miembro no cambia el estado lógico del objeto (la forma en que el objeto aparece en el mundo exterior), incluso si modifica el estado físico (la forma en que el objeto se ve debajo del capó ).
El estado lógico es la forma en que el objeto aparece ante los observadores externos. No está directamente relacionado con el estado físico y, de hecho, puede que ni siquiera se almacene como estado físico. Mientras los observadores externos no puedan ver ningún cambio, el estado lógico es constante, incluso si volteas cada bit en el objeto.
El estado físico, también conocido como estado a nivel de bits, es la forma en que el objeto se almacena en la memoria. Este es el meollo del objeto, los 1s y 0s en bruto que componen sus datos. Un objeto solo es físicamente constante si su representación en la memoria nunca cambia.
Tenga en cuenta que C ++ basa la const
en el estado lógico, no en el estado físico.
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;
}
Tenga en cuenta que, si bien técnicamente podría usar const_cast
en this
para que no esté calificado como const_cast
, realmente, REALMENTE no debería, y debería usar mutable
lugar. Un const_cast
puede invocar un comportamiento indefinido cuando se usa en un objeto que en realidad es const
, mientras que mutable
está diseñado para ser seguro de usar. Sin embargo, es posible que se encuentre con este código extremadamente antiguo.
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));
}
};
Esto evita la duplicación innecesaria de código.
Al igual que con los punteros regulares, si this
es volatile
(incluyendo const volatile
), se carga desde la memoria cada vez que se accede, en lugar de ser almacenado en caché. Esto tiene los mismos efectos en la optimización que declarar cualquier otro puntero volatile
, por lo que se debe tener cuidado.
Tenga en cuenta que si una instancia es cv-cualificado, las únicas funciones miembro que se permite el acceso a funciones son miembros cuyas this
puntero es al menos tan cv-calificada como la propia instancia:
- Las instancias no cv pueden acceder a cualquier función miembro.
-
const
instanciasconst
pueden acceder aconst volatile
funcionesconst
yconst volatile
. -
volatile
instanciasvolatile
pueden acceder a funcionesvolatile
yconst volatile
. -
const volatile
instanciasconst volatile
pueden acceder a funcionesconst volatile
.
Este es uno de los principios clave de la corrección 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.
este puntero ref-calificadores
De manera similar a this
calificadores de CV, también podemos aplicar ref-calificadores a *this
. Los ref-calificadores se utilizan para elegir entre semántica de referencia normal y rvalor, lo que permite al compilador usar la semántica de copiar o mover según sea más apropiado, y se aplican a *this
lugar de this
.
Tenga en cuenta que a pesar de los ref-calificadores que usan la sintaxis de referencia, this
sigue siendo un puntero. También tenga en cuenta que los ref-calificadores no cambian realmente el tipo de *this
; es más fácil describir y entender sus efectos mirándolos como si lo hicieran.
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 función miembro no puede tener sobrecargas con y sin ref-calificadores; El programador tiene que elegir entre uno u otro. Afortunadamente, cv-qualifiers se puede utilizar junto con ref-qualifiers, lo que permite seguir las reglas de corrección de 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&& {}
};