C++
Ten wskaźnik
Szukaj…
Uwagi
this
wskaźnik jest kluczowe dla C ++ therfore nie jest potrzebne do wdrożenia tej biblioteki. I nie zapominaj, this
to wskaźnik! Więc nie możesz zrobić:
this.someMember();
Podczas uzyskiwania dostępu do funkcji składowych lub zmiennych składowych ze wskaźników za pomocą symbolu strzałki ->
:
this->someMember();
Inne pomocne linki do lepszego zrozumienia this
wskaźnika:
http://www.geeksforgeeks.org/this-pointer-in-c/
https://www.tutorialspoint.com/cplusplus/cpp_this_pointer.htm
ten wskaźnik
Wszystkie niestatyczne funkcje składowe mają ukryty parametr, wskaźnik do instancji klasy, o nazwie this
; ten parametr jest cicho wstawiany na początku listy parametrów i obsługiwany w całości przez kompilator. Gdy dostęp do członka klasy jest uzyskiwany w ramach funkcji członka, jest on uzyskiwany w this
po cichu; pozwala to kompilatorowi na użycie jednej niestatycznej funkcji składowej dla wszystkich instancji i pozwala funkcji wywoływać polimorficznie inne funkcje składowe.
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; }
W konstruktorze można this
bezpiecznie wykorzystać (domyślnie lub jawnie) do uzyskania dostępu do dowolnego pola, które zostało już zainicjowane, lub dowolnego pola w klasie nadrzędnej; i odwrotnie (dostęp niejawny lub jawny) do pól, które nie zostały jeszcze zainicjowane, lub dowolnych pól w klasie pochodnej, jest niebezpieczny (ze względu na to, że klasa pochodna nie została jeszcze zbudowana, a zatem jej pola nie zostały zainicjowane ani nie istnieją). Nie jest również niebezpieczne wywoływanie wirtualnych funkcji składowych przez this
w konstruktorze, ponieważ żadne pochodne funkcje klasowe nie będą brane pod uwagę (ze względu na to, że klasa pochodna nie jest jeszcze budowana, a zatem jej konstruktor nie aktualizuje jeszcze vtable).
Zauważ też, że będąc w konstruktorze, typem obiektu jest typ, który ten konstruktor konstruuje. Dzieje się tak nawet wtedy, gdy obiekt jest zadeklarowany jako typ pochodny. Na przykład w poniższym przykładzie ctd_good
i ctd_bad
są typu CtorThisBase
wewnątrz CtorThisBase()
, a typ CtorThis
wewnątrz CtorThis()
, nawet jeśli ich typem kanonicznym jest CtorThisDerived
. Ponieważ klasy bardziej pochodne są konstruowane wokół klasy podstawowej, instancja stopniowo przechodzi przez hierarchię klas, aż staje się w pełni skonstruowaną instancją zgodną z przeznaczeniem.
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);
Dzięki tym klasom i funkcjom członkowskim:
- W dobrym konstruktorze dla
ctd_good
:-
CtorThisBase
jest w pełni zbudowany do czasu wprowadzenia konstruktoraCtorThis
. Dlategos
jest w prawidłowym stanie podczas inicjowaniai
, i dlatego można uzyskać do niego dostęp. -
i
jest inicjowany przed osiągnięciemj(this->i)
. Dlategoi
jest w prawidłowym stanie podczas inicjowaniaj
, i dlatego można uzyskać do niego dostęp. -
j
jest inicjalizowane przed osiągnięciemk(j)
. Dlategoj
jest w prawidłowym stanie podczas inicjowaniak
, a zatem można uzyskać do niego dostęp.
-
- W złym konstruktorze dla
ctd_bad
:-
k
jest inicjalizowane po osiągnięciuj(this->k)
. Dlategok
jest w niepoprawnym stanie podczas inicjowaniaj
, a dostęp do niego powoduje niezdefiniowane zachowanie. -
CtorThisDerived
jest budowany dopiero poCtorThis
. Dlategob
jest w nieprawidłowym stanie podczas inicjowaniak
, a dostęp do niego powoduje niezdefiniowane zachowanie. - Przedmiotem
ctd_bad
jest wciążCtorThis
aż pozostawiaCtorThis()
, a nie zostaną zaktualizowane do korzystaniaCtorThisDerived
vtable „s ażCtorThisDerived()
. Dlategovirt_func()
wywołaCtorThis::virt_func()
, niezależnie od tego, czy ma to wywoływać, czyCtorThisDerived::virt_func()
.
-
Korzystanie z tego wskaźnika w celu uzyskania dostępu do danych członka
W tym kontekście użycie this
wskaźnika nie jest całkowicie konieczne, ale sprawi, że twój kod będzie bardziej czytelny, wskazując, że dana funkcja lub zmienna należy do klasy. Przykład w tej sytuacji:
// 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;
}
Zobacz to w akcji tutaj .
Używanie tego wskaźnika do rozróżnienia danych elementu i parametrów
To jest rzeczywiście przydatna strategia odróżnienia danych członka od parametrów ... Weźmy ten przykład:
// 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();
}
Tutaj możesz zobaczyć w konstruktorze, że wykonujemy następujące czynności:
this->name = name;
Tutaj widać, że przypisujemy nazwę parametru do nazwy zmiennej prywatnej z klasy Dog (this-> name).
Aby zobaczyć wynik powyższego kodu: http://cpp.sh/75r7
ten wskaźnik kwalifikatory CV
this
może być również cv wykwalifikowany, tak samo jak każdy inny wskaźnik. Jednak ze względu na brak this
parametru na liście parametrów wymagana jest do tego specjalna składnia; kwalifikatory cv są wymienione po liście parametrów, ale przed treścią funkcji.
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*
};
Jak this
jest parametrów, funkcja może być przeciążony w oparciu o this
cv-kwalifikacjach (ów) .
struct CVOverload {
int func() { return 3; }
int func() const { return 33; }
int func() volatile { return 333; }
int func() const volatile { return 3333; }
};
Kiedy this
jest const
(w tym const volatile
), funkcja nie jest w stanie napisać do zmiennych składowych przez nią, czy jawnie lub niejawnie. Jedynym wyjątkiem są mutable
zmienne mutable
, które można zapisać niezależnie od ciągłości. Z tego powodu const
służy do wskazania, że funkcja członka nie zmienia stanu logicznego obiektu (sposób, w jaki obiekt wygląda na świat zewnętrzny), nawet jeśli zmienia stan fizyczny (sposób, w jaki obiekt wygląda pod maską) ).
Stan logiczny to sposób, w jaki obiekt wydaje się obserwatorom zewnętrznym. Nie jest bezpośrednio związany ze stanem fizycznym, a nawet może nie być przechowywany jako stan fizyczny. Dopóki zewnętrzni obserwatorzy nie widzą żadnych zmian, stan logiczny jest stały, nawet jeśli odwrócisz każdy bit w obiekcie.
Stan fizyczny, znany również jako stan bitowy, to sposób przechowywania obiektu w pamięci. To jest drobiazgowość obiektu, surowe 1 i 0, które składają się na jego dane. Obiekt jest fizycznie stały tylko wtedy, gdy jego reprezentacja w pamięci nigdy się nie zmienia.
Należy pamiętać, że C ++ podstawy const
ności od stanu logicznego, a nie stanu fizycznego.
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;
}
Należy pamiętać, że podczas technicznie przydałby const_cast
na this
, aby go non-cv-wykwalifikowanych, naprawdę, naprawdę nie powinno, i powinna wykorzystać mutable
zamiast. const_cast
może wywoływać niezdefiniowane zachowanie, gdy jest używany na obiekcie, który faktycznie jest const
, podczas gdy mutable
jest zaprojektowana tak, aby była bezpieczna w użyciu. Możliwe jest jednak, że natrafisz na to w bardzo starym kodzie.
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));
}
};
Zapobiega to niepotrzebnemu powielaniu kodu.
Podobnie jak w przypadku zwykłych wskazówek, jeśli this
jest volatile
(w tym const volatile
), to jest ładowany z pamięci za każdym razem, gdy jest dostępne, zamiast być buforowane. Ma to taki sam wpływ na optymalizację, jak deklarowanie jakiegokolwiek innego wskaźnika volatile
, dlatego należy zachować ostrożność.
Zauważ, że jeśli instancja jest zakwalifikowana do cv, jedynymi funkcjami składowymi, do których ma dostęp, są funkcje składowe, których this
wskaźnik jest co najmniej tak samo zakwalifikowany do cv, jak sama instancja:
- Instancje inne niż cv mogą uzyskać dostęp do dowolnych funkcji członkowskich.
- Instancje
const
mają dostęp do funkcjiconst
iconst volatile
. - wystąpienia
volatile
mogą uzyskać dostęp do funkcjivolatile
iconst volatile
. -
const volatile
instancje mogą uzyskać dostęp doconst volatile
funkcji.
Jest to jedna z kluczowych zasad const
poprawności .
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.
ten wskaźnik Ref-Qualifiers
Podobnie jak w przypadku this
kwalifikatorów cv, możemy również zastosować *this
kwalifikatory ref . Kwalifikatory odniesienia służą do wyboru semantyki odniesienia normalnej i wartości rvalue, umożliwiając kompilatorowi użycie semantyki kopiowania lub przenoszenia w zależności od tego, które są bardziej odpowiednie, i są stosowane do *this
zamiast this
.
Zauważ, że pomimo kwalifikatorów odniesienia korzystających ze składni referencyjnej, this
samo w sobie jest wskaźnikiem. Zauważ też, że kwalifikatory ref nie zmieniają typu *this
; po prostu łatwiej jest opisać i zrozumieć ich skutki, patrząc na nich tak, jakby to zrobili.
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
Funkcja członka nie może mieć przeciążeń zarówno z kwalifikatorami referencyjnymi, jak i bez nich; programista musi wybierać między jednym a drugim. Na szczęście kwalifikatory cv mogą być używane w połączeniu z kwalifikatorami referencyjnymi, umożliwiając przestrzeganie reguł const
poprawności .
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&& {}
};