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:-
CtorThisBasese construye completamente cuando seCtorThisconstructorCtorThis. Por lo tanto,sencuentra en un estado válido al inicializariy, por lo tanto, se puede acceder a él. -
ise inicializa antes de que se alcancej(this->i). Por lo tanto,iestá en un estado válido al inicializarj, y por lo tanto se puede acceder a él. -
jse inicializa antes de alcanzark(j). Por lo tanto,jencuentra en un estado válido al inicializark, y por lo tanto se puede acceder a él.
-
- En el constructor malo, para
ctd_bad:-
kse inicializa después de alcanzarj(this->k). Por lo tanto,kestá en un estado no válido al inicializarj, y acceder a él causa un comportamiento indefinido. -
CtorThisDerivedno se construye hasta después de queCtorThisse construya. Por lo tanto,bencuentra en un estado no válido al inicializark, y acceder a él provoca un comportamiento indefinido. - El objeto
ctd_badsigue siendo unCtorThishasta que saleCtorThis(), y no se actualiza para utilizarCtorThisDerivedvtable '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.
-
constinstanciasconstpueden acceder aconst volatilefuncionesconstyconst volatile. -
volatileinstanciasvolatilepueden acceder a funcionesvolatileyconst volatile. -
const volatileinstanciasconst volatilepueden 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&& {}
};