¿Cuándo debo hacer un uso explícito del puntero `this`?

97

¿Cuándo debería escribir explícitamente this->memberen un método de una clase?

dmckee --- ex-gatito moderador
fuente
16
Estoy seguro de que esto es un engaño, pero por supuesto es inescrutable. ¡No es la primera vez que desearía que este puntero se llamara self!
5
No solo eso, desearía que fuera una referencia.
rlbond
2
Mismo. : | He aquí por qué, por cierto: research.att.com/~bs/bs_faq2.html#this
GManNickG
11
Este método obviamente no funciona si la persona no conoce la respuesta.
Pregunte el
3
@JohnH .: Hm, parece que research.att.com/~bs/es ahora stroustrup.com. Nuevo enlace: stroustrup.com/bs_faq2.html#this
GManNickG

Respuestas:

118

Por lo general, no this->es necesario , está implícito.

A veces, existe una ambigüedad de nombre, donde se puede usar para eliminar la ambigüedad de los miembros de la clase y las variables locales. Sin embargo, aquí hay un caso completamente diferente donde this->se requiere explícitamente.

Considere el siguiente código:

template<class T>
struct A {
   int i;
};

template<class T>
struct B : A<T> {

    int foo() {
        return this->i;
    }

};

int main() {
    B<int> b;
    b.foo();
}

Si omite this->, el compilador no sabe cómo tratar i, ya que puede existir o no en todas las instancias de A. Para decirle que iefectivamente es miembro de A<T>, para cualquiera T, this->se requiere el prefijo.

Nota: aún es posible omitir el this->prefijo usando:

template<class T>
struct B : A<T> {

    using A<T>::i; // explicitly refer to a variable in the base class

    int foo() {
        return i; // i is now known to exist
    }

};
Pedir
fuente
8
Buen uso de la declaración de uso :)
Faisal Vali
3
Este es un caso particularmente desagradable. Me ha mordido antes.
Jason Baker
5
Esta puede ser una pregunta tonta, pero no entiendo por qué ipodría no existir en A. ¿Podría darme un ejemplo?
Cam Jackson
1
@CamJackson Probé el código en Visual Studio. los resultados son los mismos sin importar "esto->" existiera o no. ¿Alguna idea?
Peng Zhang
8
@CamJackson: Se pueden especializar clases en el tipo:template<> struct A<float> { float x; };
Macke
31

Si declara una variable local en un método con el mismo nombre que un miembro existente, tendrá que usar this-> var para acceder al miembro de la clase en lugar de a la variable local.

#include <iostream>
using namespace std;
class A
{
    public:
        int a;

        void f() {
            a = 4;
            int a = 5;
            cout << a << endl;
            cout << this->a << endl;
        }
};

int main()
{
    A a;
    a.f();
}

huellas dactilares:

5
4

PaV
fuente
1
Mejor usaría cout << A :: a << endl; en lugar. "esto" no es importante en este caso.
siddhant3s
3
Preferiría simplemente evitar el choque de nombres con convenciones como "m_a" o "a_".
Tom
19

Hay varias razones por las que podría necesitar usar this puntero de forma explícita.

  • Cuando desee pasar una referencia a su objeto a alguna función.
  • Cuando hay un objeto declarado localmente con el mismo nombre que el objeto miembro.
  • Cuando intentas acceder a miembros de clases base dependientes .
  • Algunas personas prefieren la notación para eliminar la ambigüedad visual de los accesos de miembros en su código.
avakar
fuente
7

Aunque generalmente no me gusta en particular, he visto a otros usar esto-> ¡simplemente para obtener ayuda de intellisense!

Brad Robinson
fuente
6
  1. Donde una variable miembro estaría oculta por una variable local
  2. Si solo quiere dejar explícitamente claro que está llamando a un método / variable de instancia


Algunos estándares de codificación utilizan el enfoque (2), ya que afirman que hace que el código sea más fácil de leer.

Ejemplo:
suponga que MyClass tiene una variable miembro llamada 'recuento'

void MyClass::DoSomeStuff(void)
{
   int count = 0;

   .....
   count++;
   this->count = count;
}
zebrabox
fuente
5

Otro caso es cuando se invocan operadores. Por ejemplo, en lugar de

bool Type::operator!=(const Type& rhs)
{
    return !operator==(rhs);
}

puedes decir

bool Type::operator!=(const Type& rhs)
{
    return !(*this == rhs);
}

Cuál podría ser más legible. Otro ejemplo es el de copiar e intercambiar:

Type& Type::operator=(const Type& rhs)
{
    Type temp(rhs);
    temp.swap(*this);
}

No sé por qué no está escrito, swap(temp)pero esto parece ser común.

rlbond
fuente
En el último caso, la nota que usted no puede llamar a un constfunción de miembro de manera temporal ( Type(rhs).swap(*this);es legal y correcto), pero una lata temporal se une a un parámetro de referencia no constante (rechazos del compilador swap(Type(rhs));, así como this->swap(Type(rhs));)
Ben Voigt
5

Hay pocos casos en los this que se debe usar el uso, y hay otros en los que se debe usar elthis puntero es una forma de resolver un problema.

1) Alternativas disponibles : para resolver la ambigüedad entre las variables locales y los miembros de la clase, como lo ilustra @ASk .

2) Sin alternativa: para devolver un puntero o una referencia thisdesde una función miembro. Esto se hace con frecuencia (y se debe hacer) cuando se sobrecarga operator+, operator-, operator=, etc:

class Foo
{
  Foo& operator=(const Foo& rhs)
  {
    return * this;
  }
};

Hacer esto permite un modismo conocido como " encadenamiento de métodos ", donde se realizan varias operaciones en un objeto en una línea de código. Como:

Student st;
st.SetAge (21).SetGender (male).SetClass ("C++ 101");

Algunos consideran esta consistencia, otros la consideran una abominación. Cuente conmigo en el último grupo.

3) Sin alternativa: Para resolver nombres en tipos dependientes. Esto surge cuando se utilizan plantillas, como en este ejemplo:

#include <iostream>


template <typename Val>
class ValHolder
{
private:
  Val mVal;
public:
  ValHolder (const Val& val)
  :
    mVal (val)
  {
  }
  Val& GetVal() { return mVal; }
};

template <typename Val>
class ValProcessor
:
  public ValHolder <Val>
{
public:
  ValProcessor (const Val& val)
  :
    ValHolder <Val> (val)
  {
  }

  Val ComputeValue()
  {
//    int ret = 2 * GetVal();  // ERROR:  No member 'GetVal'
    int ret = 4 * this->GetVal();  // OK -- this tells compiler to examine dependant type (ValHolder)
    return ret;
  }
};

int main()
{
  ValProcessor <int> proc (42);
  const int val = proc.ComputeValue();
  std::cout << val << "\n";
}

4) Alternativas disponibles: como parte del estilo de codificación, para documentar qué variables son variables miembro en lugar de variables locales. Prefiero un esquema de nomenclatura diferente donde los miembros varibales nunca pueden tener el mismo nombre que los locales. Actualmente estoy usando mNamepara miembros y namepara locales.

John Dibling
fuente
4

Solo tiene que usar this-> si tiene un símbolo con el mismo nombre en dos espacios de nombres potenciales. Toma por ejemplo:

class A {
public:
   void setMyVar(int);
   void doStuff();

private:
   int myVar;
}

void A::setMyVar(int myVar)
{
  this->myVar = myVar;  // <- Interesting point in the code
}

void A::doStuff()
{
  int myVar = ::calculateSomething();
  this->myVar = myVar; // <- Interesting point in the code
}

En los puntos interesantes del código, la referencia a myVar se referirá a la myVar local (parámetro o variable). Para acceder al miembro de la clase también llamado myVar, necesita usar explícitamente "this->".

Joe Schneider
fuente
Este es el único uso this->que es trivial de evitar (simplemente dé a la variable local un nombre diferente). Todos los usos realmente interesantes de thisni siquiera se mencionan en esta respuesta.
cmaster - reinstalar a monica
4

Los otros usos para esto (como pensé cuando leí el resumen y la mitad de la pregunta ...), sin tener en cuenta (mal) la desambiguación de nombres en otras respuestas, son si desea lanzar el objeto actual, vincularlo en un objeto de función o utilícelo con un puntero a miembro.

Casts

void Foo::bar() {
    misc_nonconst_stuff();
    const Foo* const_this = this;
    const_this->bar(); // calls const version

    dynamic_cast<Bar*>(this)->bar(); // calls specific virtual function in case of multi-inheritance
} 

void Foo::bar() const {}

Unión

void Foo::baz() {
     for_each(m_stuff.begin(), m_stuff.end(),  bind(&Foo:framboozle, this, _1));        
     for_each(m_stuff.begin(), m_stuff.end(), [this](StuffUnit& s) { framboozle(s); });         
} 

void Foo::framboozle(StuffUnit& su) {}

std::vector<StuffUnit> m_stuff;

ptr-to-member

void Foo::boz() {
    bez(&Foo::bar);
    bez(&Foo::baz);
} 

void Foo::bez(void (Foo::*func_ptr)()) {
    for (int i=0; i<3; ++i) {
        (this->*func_ptr)();
    }
}

Espero que ayude a mostrar otros usos de esto además de este-> miembro.

Macke
fuente
3

Necesita usar thispara eliminar la ambigüedad entre parámetros / variables locales y variables miembro.

class Foo
{
protected:
  int myX;

public:
  Foo(int myX)
  {
    this->myX = myX; 
  }
};
Brian R. Bondy
fuente
2
No, no lo necesitas , puedes usarlo . También puede usar un nombre diferente para el argumento de la función, que tiene la ventaja de no tener dos entidades con el mismo nombre.
cmaster - reinstalar a monica
3

El principal (o puedo decir, el único) propósito del thispuntero es que apunte al objeto utilizado para invocar una función miembro.

Con base en este propósito, podemos tener algunos casos en los que solo el uso del thispuntero puede resolver el problema.

Por ejemplo, tenemos que devolver el objeto de invocación en una función miembro con argumento es un mismo objeto de clase:

class human {

... 

human & human::compare(human & h){
    if (condition)
        return h;       // argument object
    else 
        return *this;   // invoking object
    }
};
Trevor
fuente
2

Encontré otro caso interesante de uso explícito del puntero "this" en el libro Effective C ++.

Por ejemplo, digamos que tiene una función constante como

  unsigned String::length() const

No desea calcular la longitud de String para cada llamada, por lo tanto, desea almacenarlo en caché haciendo algo como

  unsigned String::length() const
  {
    if(!lengthInitialized)
    {
      length = strlen(data);
      lengthInitialized = 1;
    }
  }

Pero esto no se compilará, está cambiando el objeto en una función constante.

El truco para resolver esto requiere fundición esta a un no-const esto :

  String* const nonConstThis = (String* const) this;

Entonces, podrás hacer arriba

  nonConstThis->lengthInitialized = 1;
Ariel
fuente
3
O puede convertirlo en lengthmutable, o incluso ponerlo en una estructura anidada. Dejar de lado la consistencia casi nunca es una buena idea.
Richard J. Ross III
3
Por favor no lo hagas. Si el miembro se va a cambiar de constfunciones de miembro, debería serlo mutable. De lo contrario, está complicando la vida para usted y otros mantenedores.
David Rodríguez - dribeas