Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


4.11.6  El puntero this

Nota:  los aspectos generales relativos a punteros a clases y a miembros de clase, son tratados en los epígrafes 4.2.1f y 4.2.1g respectivamente.

§1  Sinopsis

Hemos dicho anteriormente ( 4.11.2b) que cada instancia de una clase tiene su propio juego de variables; propiedades privativas o heredadas (según el caso), y que unas y otras se direccionan del mismo modo. Sin embargo, aunque esto es cierto para las propiedades no estáticas ( 4.11.7); no lo es para los métodos. Existe una sola versión de los métodos de clase en el objeto-clase ( 4.11.5), que solo pueden ser invocados por los objetos de la clase (podríamos decir que son funciones de uso restringido). Pero entonces surge una cuestión: cuando un objeto invoca uno de estos métodos ¿Cómo sabe la función sobre que instancia de la clase debe operar?. O dicho con otras palabras: ¿Que juego de variables debe utilizar ?.

Para fijar ideas consideremos el caso del ejemplo siguiente:

#include <iostream>

using namespace std;

class X {
  public:
  int x;
  void pow2() {cout << "El cuadrado es: " << x*x << endl; }
};

void main() {       // =========
  X x1;             // x1 una instancia de X
   x1.x = 2;
  X x2;             // x2 otra instancia de X
   x2.x = 5;
  x1.pow2();        // M.5: invocación de func desde x1
  x2.pow2();        // M.6: invocación de func desde x2
}

Salida:

El cuadrado es: 4
El cuadrado es: 25

Observando la salida se hace evidente que en uno y otro caso, la invocación a pow2 se ha realizado correctamente (aunque no se le hayan pasado parámetros !!). El compilador sabe sobre que instancia debe operar y ha utilizado el juego de variables correspondiente.

Recuerde que aunque coloquialmente se pueda decir que x2.pow2() es la "Invocación del método pow2() del objeto x2", en el fondo es incorrecto. No existe tal pow2() del objeto x2. Es más cercano a la realidad decir: "es la invocación del método pow2() de la clase X utilizando el juego de variables del objeto x2".

§2  El argumento oculto

La respuesta a como las funciones miembro operan con el conjunto de variables de los objetos para los que son invocadas, está en que C++ incluye en tales funciones (como pow2) un parámetro especial oculto denominado this (es una palabra clave C++). this es un puntero al objeto invocante [2]. Este puntero es pasado automáticamente por el compilador como argumento en todas las llamadas a funciones miembro (no estáticas). Como su inclusión es automática y transparente para el programador, es frecuente referirse a él como argumento implícito u oculto.

El resultado es que cuando el compilador encuentra una invocación (con o sin argumentos) del tipo x.pow2(), calcula la dirección del objeto (&x), y realiza una invocación del tipo pow2(&x), utilizando esta dirección como valor del argumento oculto this. Por supuesto, el compilador añade por su cuenta el argumento correspondiente en la definición de la función X::pow2().

Nota: en realidad, el objeto x de una clase C tiene existencia en memoria en forma de una estructura que contiene los miembros no estáticos de la clase. La dirección del objeto es la del primer miembro de esta estructura. Las direcciones del resto de miembros se consideran desplazamientos respecto a esta dirección inicial. Esta arquitectura es la clave del funcionamiento de los punteros-a-clases ( 4.2.1f) y punteros-a-miembros ( 4.2.1g).


En el caso del ejemplo anterior las cosas ocurren "como si" la definición de la clase X hubiese sido:

class X {
  public:
  int x;
  void pow2(X* this) {
    cout << "El cuadrado es: " << this->x * this->x << endl;
  }
};

y a su vez, las invocaciones de M.5 y M.6 se sustituyen por:

  X* xptr1 = &x1;   // puntero-a-tipoX señalando a x1
  X* xptr2 = &x2;   // puntero-a-tipoX señalando a x2
  X::pow2(xptr1);   // invocación de X::func referida a x1
  X::pow2(xptr2);   // invocación de X::func referida a x2


Tenga en cuenta que estas líneas solo tratan de ofrecer una imagen didáctica, ya que this es un puntero muy especial que no puede ser declarado explícitamente, por lo que la definición void pow2(X* this) { /* ... */ } no sería válida. Tampoco puede tomarse su dirección o ser utilizado como Rvalue para una asignación del tipo this = x (si puede en cambio ser utilizado como Lvalue). Por otra parte, las invocaciones en la forma X::pow2(xptr); tampoco son correctas. La forma sintácticamente correcta más parecida a la imagen que queremos transmitir sería la siguiente:

#include <iostream>
using namespace std;

class X {
  public:
  int x;
  void pow2(X* xpt) {
    cout << "El cuadrado es: " << xpt->x * xpt->x << endl;
  }
};

void main() {            // ======================
  X x1;                  // x1 una instancia de X
    x1.x = 2;
  X x2;                  // x2 otra instancia de X
    x2.x = 5;
  X* xpt1 = &x1;         // puntero-a-tipoX señalando a x1
  X* xpt2 = &x2;         // puntero-a-tipoX señalando a x2
  x1.pow2(xpt1);
  x2.pow2(xpt2);
}


  El problema de identificación antes señalado no existe para las funciones miembro estáticas; veremos ( 4.11.7) que no tienen puntero this, por lo que este tipo de funciones pueden ser invocadas sin referirse a ningún objeto particular. La contrapartida es que un método estático no puede acceder a miembros no estáticos sin utilizar los selectores de miembro . o -> ( 4.11.2e). Es decir, hay que indicarle explícitamente sobre que juego de variables debe operar.

Las funciones friend ( 4.11.2a1) que no son miembros de ninguna clase tampoco disponen de este argumento oculto (no disponen de puntero this). Tampoco las estructuras anónimas ( 4.11.3a)

§3  this es una variable local 

Como ocurre con todos los parámetros de funciones, resulta que this es una variable local (puntero) presente en el cuerpo de cualquier función miembro no estática. this no necesita ser declarado, y es raro que sea referenciado explícitamente en la definición de alguna función, lo que no es obstáculo para que sea utilizado dentro de la propia función para referenciar a los miembros [3].

Según lo anterior resulta evidente que, como tal variable local, esta palabra clave no puede ser usada fuera del cuerpo de un método de una clase.

Nota: el conocimiento de los dos puntos mencionados (que es una variable local y que es un puntero al objeto) son la clave para manejar this con cierta soltura si las circunstancias lo requieren. Por ejemplo, si se invoca x.func(y), donde y es un miembro de X, la variable this adopta el valor &x e y adopta el valor this->y, lo que equivale a x.y.


En las funciones-miembro no-constantes ( 3.2.1c) de una clase C, el tipo de this es C* ( 4.2.1f). Por contra, en los métodos constantes su tipo es const C* (puntero-a-constante) [1].


§4  En el siguiente ejemplo se definen dos clases, idénticas salvo en la forma de referenciar a sus miembros; en una se utiliza el puntero this de forma explícita, en otra de forma implícita, ambas son equivalentes, aunque es más normal utilizar la forma implícita.

#include <iostream.h>

class X {
  int x;
  public:
  int getx() { return x; }
  void putx (int i) { x = i; }
};

class Y {
  int x;
  public:
  int getx() { return this->x; }
  void putx (int i) { this->x = i; }
};

void main() {        // ==========
  X x1;
  x1.putx(10);
  cout << "Valor de x1.x: " << x1.getx() << endl;
  Y y1;
  y1.putx(20);
  cout << "Valor de y1.x: " << y1.getx() << endl;
}

Salida:

Valor de x1.x: 10
Valor de y1.x: 20


§5  A continuación se expone una variación del ejemplo presentado al tratar del acceso a miembros de clases desde funciones miembro ( 4.11.2e). En esta ocasión se utiliza el puntero this en vez del operador de acceso a ámbito.

#include <iostream.h>

int x = 10;            // L.3:
class CL {
  int x, y;            // L.5: privados por defecto
  public:
  void setx(int i) { x = i; }  // L.7:
  void sety(int y) { this->y = y; }
  int getxy();
};
int CL::getxy() { return x + y; }

int main() {           // =============
  CL c;
  c.setx(1);
  c.sety(2);
  cout << "X + Y == " << c.getxy() << endl;
  return 0;
}

Salida:

X + Y == 3

Comentario

En esta ocasión la presencia explícita del puntero this en el cuerpo de sety (L.8) se justifica porque la variable local y oculta al miembro del mismo nombre.

Puesto que, según hemos declarado , el puntero this referencia al objeto invocante, la expresión *this representa al objeto, por lo que suele utilizarse cuando se quiere devolver el objeto invocado por la función-miembro.


§6  Considere una variación sobre el primer ejemplo , en el que modificamos ligeramente la definición del método pow2 haciendo que devuelva un objeto.

#include <iostream>
using namespace std;

class X {
  public:
  int x;
  X pow2() {
    x = x * x;
    return *this;
  }
};

void main() {              // =========
  X x1, x2;                // instancias de X
  x1.x = 2; x2.x = 5;
  x1.pow2();               // invocación de pow2 desde x1
  x2.pow2();               // invocación de pow2 desde x2
  cout << "x1 = " << x1.x << endl;
  cout << "x2 = " << x2.x << endl;
  x2 = x1.pow2();          // M.7:
  cout << "x2 = " << x2.x << endl;
}

Salida:

x1 = 4
x2 = 25
x2 = 16

Comentario

Comprobamos que las salidas son las mismas que en el primer ejemplo. Las operaciones se han realizado sobre las variables del objeto correspondiente, pero además, en este caso el método devuelve un objeto, por lo que puede ser utilizado como Rvalue de la asignación M.7. En consecuencia, el valor 4 del objeto x1 es transformado a 4 * 4 = 16 en la invocación del lado derecho de la asignación. Posteriormente este valor es asignado al objeto x2. Este es el resultado que se obtiene en la tercera salida.


§7  La expresión *this también puede ser utilizada para devolver una referencia ( 4.2.3) al objeto invocado por la función-miembro. El ejemplo puede modificarse para hacer que pow2 devuelva una referencia al objeto:

#include <iostream>
using namespace std;

class X {
  public:
  int x;
  X& pow2() {
    x = x * x;
    return *this;
  }
};

void main() {            // ==============
  X x1, x2;              // instancias de X
  x1.x = 2; x2.x = 5;
  x1.pow2();             // invocación de func desde x1
  x2.pow2();             // invocación de func desde x2
  cout << "x1 = " << x1.x << endl;
  cout << "x2 = " << x2.x << endl;
  x2.pow2() = x1.pow2();     // M.7:
  cout << "x2 = " << x2.x << endl;
}

Salida:

x1 = 4
x2 = 25
x2 = 16

Comentario

Las salidas son las mismas que en el ejemplo anterior, aunque con una diferencia significativa: la referencia devuelta por la invocación pow2 sobre el objeto x1 (en el lado derecho de la asignación M.7), es utilizado como Rvalue de la asignación, y aplicada a la referencia devuelta por la asignación del lado izquierdo (que es ahora un Lvalue).

Esta capacidad de las referencias: ser un Rvalue cuando se utilizan a la derecha de una asignación y un Lvalue, cuando se sitúan a la izquierda, es precisamente la razón por la que se introdujeron en el lenguaje, siendo una propiedad muy utilizada en la sobrecarga de las funciones-operador. En el capítulo dedicado a la sobrecarga del operador preincremento se expone en detalle esta característica ( 4.9.18c).


  Otros ejemplos en: 4.9.18.e y 4.9.18.e

  Inicio.


[1]  Los usuarios de GNU Cpp deben tener en cuenta que este compilador el tipo de this es C* const, es decir: puntero-constante-a-tipoC ( 4.2.1e). Lo que a la postre significa que no puede recibir asignaciones en su ámbito de visibilidad (el cuerpo de los métodos no estáticos). Sin embargo, existen muchos otros compiladores en los que esto no es así (this no es un puntero constante). Para mantener la compatibilidad, g++ dispone de la opción de compilación -fthis-is-variable, que permite asignar a this las características tradicionales.

Nota: puede conseguirse el mismo resultado con la opción -traditional, aunque esta última tiene otros efectos adicionales.

[2]  Se puede expresar de varias formas según el punto de vista o la imagen mental que se prefiera. Por ejemplo, desde el punto de vista de la POO clásica: "puntero al objeto sobre el que se aplica el método". También: "puntero al objeto para el que se invoca el método", o: "puntero al objeto cuyas propiedades serán utilizadas en la invocación".

[3]  Lo de que sea "rara" su utilización es desde luego relativo. No es demasiado frecuente en programas simples de usuario, pero las aplicaciones sofisticadas, y las macros de los propios compiladores están a veces plagadas de referencias a este puntero.