Kiedy należy jawnie używać wskaźnika „this”?


100

Kiedy powinienem jawnie pisać this->memberw metodzie klasy?


16
Jestem pewien, że to oszustwo, ale oczywiście nie można go przeszukać. Nie po raz pierwszy chciałbym, żeby ten wskaźnik został nazwany ja!

5
Nie tylko to, chciałbym, żeby to był punkt odniesienia.
rlbond

2
Podobnie. : | A tak przy okazji: research.att.com/~bs/bs_faq2.html#this
GManNickG,

11
Ta metoda oczywiście nie działa, jeśli osoba nie zna odpowiedzi.
ZAPYTAJ

3
@JohnH .: Hm, wygląda na research.att.com/~bs/to , że jest teraz stroustrup.com. Nowy link: stroustrup.com/bs_faq2.html#this
GManNickG

Odpowiedzi:


121

Zwykle nie musisz tego robić this->.

Czasami występuje niejednoznaczność nazwy, w której można ją wykorzystać do ujednoznacznienia członków klasy i zmiennych lokalnych. Jednak tutaj jest zupełnie inny przypadek, w którym this->jest to wyraźnie wymagane.

Rozważ następujący kod:

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();
}

Jeśli pominiesz this->, kompilator nie będzie wiedział, jak traktować i, ponieważ może istnieć lub nie we wszystkich wystąpieniach A. Aby stwierdzić, że ijest członkiem A<T>, dla każdego T, this->wymagany jest przedrostek.

Uwaga: nadal można pominąć this->prefiks, używając:

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
    }

};

8
Przyjemny użytek z deklaracji using :)
Faisal Vali

4
To szczególnie nieprzyjemny przypadek. Już wcześniej mnie to ugryzło.
Jason Baker

5
To może być głupie pytanie, ale nie rozumiem, dlaczego imoże nie istnieć w A. Czy mógłbym dostać przykład?
Cam Jackson,

1
@CamJackson Wypróbowałem kod w Visual Studio. wyniki są takie same, niezależnie od tego, czy „to->” istniało, czy nie. Dowolny pomysł?
Peng Zhang

8
@CamJackson: Można specjalizować się w zajęciach typu:template<> struct A<float> { float x; };
Macke,

31

Jeśli deklarujesz zmienną lokalną w metodzie o tej samej nazwie, co istniejący element członkowski, będziesz musiał użyć this-> var, aby uzyskać dostęp do elementu członkowskiego klasy zamiast do zmiennej lokalnej.

#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();
}

wydruki:

5
4


1
Lepiej użyłbym cout << A :: a << endl; zamiast. „to” nie ma znaczenia w tym przypadku.
siddhant3s

3
Wolałbym po prostu uniknąć konfliktu nazwy z konwencjami takimi jak „m_a” czy „a_”.
Tom

19

Istnieje kilka powodów, dla których może być konieczne thisjawne użycie wskaźnika.

  • Gdy chcesz przekazać odniesienie do swojego obiektu do jakiejś funkcji.
  • Gdy istnieje lokalnie zadeklarowany obiekt o takiej samej nazwie jak obiekt członkowski.
  • Kiedy próbujesz uzyskać dostęp do członków zależnych klas bazowych .
  • Niektórzy ludzie wolą notację, aby wizualnie rozróżnić dostęp członków w ich kodzie.

7

Chociaż zwykle mi się to nie podoba, widziałem, jak inni używają tego -> po prostu do uzyskania pomocy od inteligencji!


6
  1. Gdzie zmienna składowa byłaby ukryta przez zmienną lokalną
  2. Jeśli chcesz tylko wyraźnie zaznaczyć, że wywołujesz metodę / zmienną instancji


Niektóre standardy kodowania używają podejścia (2), ponieważ twierdzą, że ułatwia to odczytanie kodu.

Przykład:
Załóżmy, że MyClass ma zmienną składową o nazwie „count”

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

   .....
   count++;
   this->count = count;
}

6

Jest kilka przypadków, w których this należy użyć using , a są inne, w których użycie thiswskaźnika jest jednym ze sposobów rozwiązania problemu.

1) Dostępne alternatywy : Aby rozwiązać niejednoznaczność między zmiennymi lokalnymi a składowymi klas, jak pokazano na @ASk .

2) Brak alternatywy: aby zwrócić wskaźnik lub odwołanie do thisfunkcji składowej. Jest to często zrobić (i powinno być zrobione) przy przeciążeniu operator+, operator-, operator=itp:

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

W ten sposób można zastosować idiom zwany „ łączeniem metod ”, w którym wykonujesz kilka operacji na obiekcie w jednej linii kodu. Jak na przykład:

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

Niektórzy uważają ten błąd, inni uważają to za obrzydliwość. Zalicz mnie do tej drugiej grupy.

3) Brak alternatywy: rozwiązywanie nazw w typach zależnych. Pojawia się podczas korzystania z szablonów, jak w tym przykładzie:

#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) Dostępne alternatywy: jako część stylu kodowania, do udokumentowania, które zmienne są zmiennymi składowymi, a które zmiennymi lokalnymi. Wolę inny schemat nazewnictwa, w którym zmienne składowe nigdy nie mogą mieć takiej samej nazwy jak lokalni. Obecnie używam mNamedla członków i namedla mieszkańców.


5

Jeszcze jeden przypadek dotyczy wywoływania operatorów. Np. Zamiast

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

możesz powiedzieć

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

Które mogłoby być bardziej czytelne. Innym przykładem jest kopiuj i zamień:

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

Nie wiem, dlaczego nie jest napisane, swap(temp)ale wydaje się, że jest to powszechne.


W ostatnim przypadku należy pamiętać, że można zadzwonić niebędącą constfunkcji członka na tymczasowy ( Type(rhs).swap(*this);jest legalne i zgodne z prawdą), ale przejściowych nie wiążą się z const parametru odniesienia (odrzuty kompilatora swap(Type(rhs));, jak również this->swap(Type(rhs));)
Ben Voigt

4

Musisz użyć this-> tylko wtedy, gdy masz symbol o tej samej nazwie w dwóch potencjalnych przestrzeniach nazw. Weź na przykład:

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
}

W interesujących punktach kodu odwołanie do myVar będzie odnosić się do lokalnego (parametru lub zmiennej) myVar. Aby uzyskać dostęp do elementu klasy zwanego również myVar, musisz jawnie użyć „this->”.


Jest to jedyne zastosowanie, this->którego należy unikać (wystarczy nadać zmiennej lokalnej inną nazwę). thisTa odpowiedź nie wspomina nawet o wszystkich naprawdę interesujących zastosowaniach .
cmaster

4

Inne zastosowania do tego (jak myślałem, kiedy czytałem podsumowanie i połowę pytania ....), Pomijając (złe) ujednoznacznienie nazewnictwa w innych odpowiedziach, to jeśli chcesz rzutować bieżący obiekt, powiązaj go w obiekcie funkcji lub użyj go ze wskaźnikiem do elementu członkowskiego.

Rzuty

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 {}

Wiążący

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)();
    }
}

Mam nadzieję, że pomoże to pokazać inne zastosowania tego niż tylko ten-> członek.


3

Musisz użyć, thisaby rozróżnić parametry / zmienne lokalne i zmienne składowe.

class Foo
{
protected:
  int myX;

public:
  Foo(int myX)
  {
    this->myX = myX; 
  }
};

2
Nie, nie potrzebujesz tego, możesz go użyć . Możesz również użyć innej nazwy dla argumentu funkcji, co ma tę zaletę, że nie ma dwóch jednostek o tej samej nazwie.
cmaster

3

Głównym (lub mogę powiedzieć, jedynym) celem thiswskaźnika jest wskazanie obiektu używanego do wywołania funkcji składowej.

Bazując na tym celu, możemy mieć przypadki, w których tylko użycie thiswskaźnika może rozwiązać problem.

Na przykład, musimy zwrócić obiekt wywołujący w funkcji składowej, której argument jest tym samym obiektem klasy:

class human {

... 

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

2

Znalazłem inny interesujący przypadek jawnego użycia wskaźnika „this” w książce Effective C ++.

Na przykład, powiedzmy, że masz funkcję const, taką jak

  unsigned String::length() const

Nie chcesz obliczać długości łańcucha dla każdego wywołania, dlatego chcesz go buforować, robiąc coś takiego

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

Ale to się nie skompiluje - zmieniasz obiekt w funkcji const.

Sztuczka, aby rozwiązać ten problem, wymaga rzutowania tego na inny element niż const this :

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

Wtedy będziesz mógł zrobić to powyżej

  nonConstThis->lengthInitialized = 1;

3
Lub możesz uczynić lengthmutable, a nawet umieścić go w zagnieżdżonej strukturze. Rzucanie konsternacji prawie nigdy nie jest dobrym pomysłem.
Richard J. Ross III

3
Proszę nie. Jeśli element członkowski ma zostać zmieniony z constfunkcji składowych, powinien być mutable. W przeciwnym razie utrudnisz życie sobie i innym opiekunom.
David Rodríguez - dribeas
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.