31 El puntero this

Para cada objeto declarado de una clase se mantiene una copia de sus datos, pero todos comparten la misma copia de las funciones de esa clase.

Esto ahorra memoria y hace que los programas ejecutables sean más compactos, pero plantea un problema.

Cada función de una clase puede hacer referencia a los datos de un objeto, modificarlos o leerlos, pero si sólo hay una copia de la función y varios objetos de esa clase, ¿cómo hace la función para referirse a un dato de un objeto en concreto?

La respuesta es: usando el puntero especial llamado this. Se trata de un puntero que tiene asociado cada objeto y que apunta a si mismo. Ese puntero se puede usar, y de hecho se usa, para acceder a sus miembros.

Volvamos al ejemplo de la clase pareja:

#include <iostream>
using namespace std;
 
class pareja {
   public:
      // Constructor
      pareja(int a2, int b2);
      // Funciones miembro de la clase "pareja"
      void Lee(int &a2, int &b2);
      void Guarda(int a2, int b2);
   private:
      // Datos miembro de la clase "pareja"
      int a, b; 
};

Para cada dato podemos referirnos de dos modos distintos, lo veremos con la función Guarda. Esta es la implementación que usamos en el capítulo 29, que es como normalmente nos referiremos a los miembros de las clases:

void pareja::Guarda(int a2, int b2) {
   a = a2;
   b = b2;
}

Veamos ahora la manera equivalente usando el puntero this:

void pareja::Guarda(int a2, int b2) {
   this->a = a2;
   this->b = b2;
}

Veamos otro ejemplo donde podemos aplicar el operador this. Se trata de la aplicación más frecuente, como veremos al implementar el constructor copia, o al sobrecargar ciertos operadores.

A veces necesitamos invocar a una función de una clase con una referencia a un objeto de la misma clase, pero las acciones a tomar serán diferentes dependiendo de si la referencia que pasamos se refiere al mismo objeto o a otro diferente, veamos cómo podemos usar el puntero this para determinar esto:

#include <iostream>
using namespace std;

class clase {
  public:
   clase() {}
   void EresTu(clase& c) {
      if(&c == this) cout << "Sí, soy yo." << endl;
      else cout << "No, no soy yo." << endl;
   }
};
   
int main() {
   clase c1, c2;
   
   c1.EresTu(c2);
   c1.EresTu(c1);
   
   return 0;
}

La función "EresTu" recibe una referencia a un objeto de la clase "clase". Para saber si se trata del mismo objeto, comparamos la dirección del objeto recibido con el valor de this, si son la misma, es que se trata del mismo objeto.

No, no soy yo.
Sí, soy yo.

Este puntero nos resultará muy útil en próximos capítulos, en los que nos encontraremos situaciones en las que es imprescindible su uso.

Cómo funciona

Para intentar comprender algo mejor cómo funciona este puntero, veremos una forma de simularlo usando un ejemplo con estructuras.

Intentaremos crear una estructura sencilla que tenga un miembro con un puntero a un objeto de su mismo tipo. Además, haremos que ese puntero contenga la dirección del propio objeto. De modo que si creamos varios objetos de esa clase, cada uno de ellos apunte a si mismo.

Después de intentarlo un rato, comprobaremos que no es posible hacer esto dentro del constructor, ya que en ese momento no tenemos referencias al objeto. Tendremos que hacerlo, pues, añadiendo un parámetro más al constructor o después de crear el objeto, añadiendo una línea obj.esto = &obj;.

#include <iostream>
using namespace std;

struct ejemplo {
    ejemplo(int v, ejemplo* e);
    int valor;
    ejemplo *esto;
};

ejemplo::ejemplo(int v, ejemplo *e) : valor(v), esto(e) {}

int main<() {
    ejemplo e1(19, &e1);

    cout << &e1 << "  " << (void*)e1.esto << endl;
    return 0;
}

Ejecutar este código en codepad.

Bien, esto es lo que hace el compilador en nuestro lugar cuando crea el puntero this. Y lo pongo en cursiva porque en realidad ese puntero no se almacena junto con el objeto. No ocupa memoria, y por lo tanto no se tiene en cuenta al calcular el tamaño de la estructura con el operador sizeof, no se crea, ni se iniciliza. El compilador sabe durante la fase de compilación dónde almacena cada objeto, y por lo tanto, puede sustituir las referencias al puntero this por sus valores en cada caso.

El puntero this nos permite hacer cosas como obtener una referencia al propio objeto como valor de retorno en determinadas funciones u operadores. En ocasiones puede resolver ambigüedades o aclarar algún código.

Palabras reservadas usadas en este capítulo

this.

Comentarios de los usuarios (8)

camila garses
2011-03-03 01:23:10

necesito porfa hacer un programa en dev c++ para el siguiente ejercicio calcular la potencia de un numero entero elevado a un numero natural solo en base a sumas

Karlyz
2013-09-01 01:02:12

Hola que tal...podrían ayudarme con los errores que me marca...De antemano GRACIAS!!

#include<iostream>
#include<set>
using namespace std;
const int maxCard = 16;
bool {false, true};
enum ErrCode {noErr,overflow};

class Set {
    int elems[maxCard];    
    int card;
    public:
           void EmptySet() {card=0;}
           bool Member (int);
           ErrCode AddElem (int);
           void RmvElem (int);
           void Copy (Set*);
           bool Equal(Set*);
           void Print();
           void Intersect (Set*,Set*);
           ErrCode Union (Set*,Set*);
};

bool Set::Member (int elem)
{
     for(int i=0; i< card; ++i)
      if (elems [i] == elem)
     return true;
     return false;
     }
/*Member */

ErrCode Set::AddElem (int elem)
{
        for( int i=0; i<card; ++i)
        if (elems[i] == elem) return noErr;
        if (card< maxCard){
                  elems[card++] = elem; return noErr;
                  } else
                  return overflow;
                  }
/*AddElem*/

void Set::RmvElem(int elem)
{
           for (int i=0; i< card;++i)       
           if (elems[i] = elem){
           for (; i< card-1; ++i)
           elems[i]= elems [i+1];
           --card;
           }
           }
/*RmvElem*/

void Set::Copy (Set*,set)
{
     for(int i=0; i<card; ++i)
     set->elems[i] = elems [i];
     set->card = card;
     }
/*Copy*/

bool Set::Equal (Set* set)
{
     if (card != set -> card) return false;
     for (int i=0; i<card; ++i)
     if(!set->Member(elems[i]))
     return false;
     return true;
     }
/*Equal*/
     
void Set::Print()
{
     cout<<"{";
     for(int i =0; i< card-1;++i)
     cout<<elems[i] <<",";
     if (card >0)
     cout<< elems[card-1];
     cout<<"}\n";
     }
/*Print*/
 
 main()
 {
       Set  s1,s2,s3;
       s1.EmptySet(); s2.EmptySet(); s3.EmptySet();
       s1.AddElem(10); s1.AddElem(20); s1.AddElem(30);s1.AddElem(40);
       s2.AddElem(30); s2.AddElem(50); s2.AddElem(10);s2.AddElem(60);
       cout<<"s1="; s1.Print();
       cout<<"s2="; s2.Print();
       s2.RmvElem(50);
       cout<<"s2 - {50}= "; s2.Print();
       if(s1.Member(20))
       cout<<"20 is in s1\n";
       s1.Intersect(&s2,&s3);
       cout<<"s1 intsec s2 =";  s3.Print();
       s1.Union(&s2,&s3);
       cout<<"s1 union s2="; s3.Print();
       if (!s1.Equal(&s2))
       cou<<"s1/= s2 \n";
       }
 /*main*/
 
anibal
2014-08-04 19:43:59

El ejemplo que usa la estructura "ejemplo". Al compilarlo tal cual como esta da un error (quitando el simbolo < en main()).

(14) : error C2065: 'e1' : undeclared identifier

Pareciera que no acepta usar e1 como parametro al no estar declarado...

Jesus Hernandez
2015-05-08 18:07:09

Quisiera que si podria ayudarme a entender bien esto;como funciona this aqui:

class x{

......

x *sig;

static x* lista;

public:

x(....){ sig = lista;lista = this; }

yo quiero crear una lista de objetos de x;y no logro entender como hacerlo y como funciona this en esta situacion en particular;agradesco de antemano la atencion a estas dudas,y espero me ayude a resolverlas;aprobecho para mencionar que este curso es exelente....gracias.

Steven R. Davidson
2015-05-08 20:30:58

Hola Jesús,

Es un diseño interesante para crear una lista dinámicamente enlazada. Básicamente, al crear un nuevo objeto, su constructor asigna el puntero, 'sig', para apuntar a la cabeza de la lista, representada por 'lista'. Posteriormente, "este nuevo objeto" (el puntero 'this') formará parte de la cabeza de la lista. Por ejemplo,

x o1;   // o1.sig = 0;  x::lista = &o1;

x o2;   // o2.sig = &o1;  x::lista = &o2;

x o3;   // o3.sig = &o2;  x::lista = &o3;

for( x *ptr = x::getLista(); ptr != 0; ptr = ptr->getSiguiente() )
  cout << ptr->getDato();

Espero que esto aclare la duda.

Steven

Maria
2016-07-04 22:50:45

Hola, tengo un problema con el constructor copia porque después de cambiar el nombre del jugador a, me lo cambia también en la copia. Además el programa casca al ir a imprimir el jugador c.

Muchas gracias

class Jugador {

char *NombreJugador;

int PuntoTotal;

public:

Jugador::Jugador(char *nombre, int a) { //constructor con valores

NombreJugador = new char[strlen(nombre) + 1];

strcpy(NombreJugador, nombre);

PuntoTotal = a;

}

Jugador::Jugador(const Jugador&j) { //constructor por copia

NombreJugador = j.NombreJugador;

PuntoTotal = j.PuntoTotal;

cout << NombreJugador << endl;

}

Jugador::Jugador() { //constructor por defecto

NombreJugador = NULL;

PuntoTotal = -1;

}

void Jugador::imprimir() {

cout << NombreJugador << endl;

cout << PuntoTotal << endl;

if (PuntoTotal = -1) {

cout << "Error" << endl;

}

}

void Jugador::modificarNombre() {

int i;

cout << NombreJugador << endl;

for (i = 0; NombreJugador[i] != '\0'; i++) {

if (NombreJugador[i] >= 'a' && NombreJugador[i] <= 'z') {

NombreJugador[i] = NombreJugador[i] - ('a' - 'A');

}

}

}

void Jugador::setPuntoTotal(int a) {

PuntoTotal = a;

}

Jugador::~Jugador() { //Destructor

delete[]NombreJugador;

cout << NombreJugador << endl;

cout << "borrado" << endl;

};

int main() {

Jugador a("Daniel"); // constructor con valores

Jugador b = a; // constructor de copia

Jugador c; // constructor con valores por defecto

a.modificarNombre();

a.setPuntoTotal(20);

a.imprimir(); // imprimir· DANIEL y 20

b.imprimir(); // imprimir· Daniel y 0

c.imprimir(); // imprimir· en mensaje de error

return 0;

} // se llamar· autom·ticamente al destructor para eliminar cada objeto

Steven R. Davidson
2016-07-05 18:05:11

Hola María,

Has copiado los punteros, 'NombreJugador', de un objeto a otro, cuando deberías creado memoria dinámicamente y copiar el contenido de la cadena a esta memoria de "este objeto". Esto es exactamente lo que hiciste en el primer constructor; es decir,

Jugador( const Jugador &j )
{
  NombreJugador = new char[strlen(j.NombreJugador) + 1];
  strcpy( NombreJugador, j.NombreJugador);
  PuntoTotal = j.PuntoTotal;
}

También tienes otros errores en el resto del código:

- Sólo debes indicar el ámbito de la clase 'Jugador' para sus miembros, si estás fuera del ámbito. Elimina la parte de 'Jugador::' si estás dentro de la clase 'Jugador'; por ejemplo,

class Jugador
{
  ...
  Jugador()  { ... }
  Jugador( char *nombre, int a )  { ... }
  Jugador( const Jugador& j )  { ... }
  ~Jugador()  { ... }

  void imprimir()  { ... }
  void modificarNombre()  { ... }
  void setPuntoTotal( int a )  { ... }
};

- En el constructor, 'Jugador( char *, int )', deberías aceptar un puntero a 'const char' si quieres permitir pasar una cadena literal (y por tanto, constante); esto es,

Jugador( const char *nombre, int a )  { ... }

- En 'imprimir()', usas el operador de asignación = cuando seguramente querías usar el operador relacional de igualdad ==. Escribes,

void imprimir()
{
  cout << NombreJugador << endl;
  cout << PuntoTotal << endl;
  if( PuntoTotal = -1 )
    cout << "Error" << endl;
}

Deberías escribir,

if( PuntoTotal == -1 )

- Como estás definiendo una clase que gestiona memoria dinámicamente, deberías sobrecargar el operador = para realizar la copia correctamente, que será algo parecido al constructor copia. Aconsejo que mires el capítulo 35 acerca de este tema; puedes dirifirte a: http://c.conclase.net/curso/index.php?cap=035b#037_punteros

- En 'main()', no usas el constructor correctamente al instanciar el primer objeto, 'a', porque debes pasar un segundo parámetro de tipo 'int'. Escribes,

int main()
{
  Jugador a("Daniel");
  ...
}

Se podría reescribir como,

int main()
{
  Jugador a("Daniel",0);
  ...
}

Espero que todo esto te sirva.

Steven

Adrian
2016-09-27 21:57:40

Entonces ¿Que hace el modificador static con las funciones si de estas solo se guarda una copia?