C++
Le pointeur
Recherche…
Remarques
Le pointeur this
est un mot-clé pour C ++, il n'y a donc pas de bibliothèque nécessaire pour l'implémenter. Et ne pas oublier c'est un pointeur! this
Donc, vous ne pouvez pas faire:
this.someMember();
Lorsque vous accédez à des fonctions membres ou à des variables membres à partir de pointeurs à l'aide du symbole flèche ->
:
this->someMember();
Autres liens utiles pour mieux comprendre le pointeur this
:
http://www.geeksforgeeks.org/this-pointer-in-c/
https://www.tutorialspoint.com/cplusplus/cpp_this_pointer.htm
ce pointeur
Toutes les fonctions membres non statiques ont un paramètre caché, un pointeur sur une instance de la classe, nommé this
; Ce paramètre est inséré en silence au début de la liste de paramètres et entièrement géré par le compilateur. Lorsqu'un membre de la classe est accédé à l'intérieur d'une fonction membre, il est accessible silencieusement à travers this
; Cela permet au compilateur d'utiliser une fonction membre non statique unique pour toutes les instances et permet à une fonction membre d'appeler d'autres fonctions membres de manière polymorphe.
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; }
Dans un constructeur, this
peut être utilisé en toute sécurité (implicitement ou explicitement) pour accéder à tout champ déjà initialisé ou à tout champ d'une classe parente; inversement, (implicitement ou explicitement) l'accès à tous les champs qui n'ont pas encore été initialisés, ou à tout champ d'une classe dérivée, est dangereux (la classe dérivée n'étant pas encore construite et ses champs n'étant ni initialisés ni existants). Il est également dangereux d'appeler des fonctions membres virtuelles via this
dans le constructeur, comme toutes les fonctions de classe dérivée ne seront pas pris en compte ( en raison de la classe dérivée pas encore en cours de construction, et donc son constructeur ne mettre à jour encore le vtable).
Notez également que, dans un constructeur, le type de l'objet est le type que ce constructeur construit. Cela est vrai même si l'objet est déclaré comme un type dérivé. Par exemple, dans l'exemple ci-dessous, ctd_good
et ctd_bad
sont de type CtorThisBase
dans CtorThisBase()
, et tapez CtorThis
dans CtorThis()
, même si leur type canonique est CtorThisDerived
. Au fur et à mesure que les classes dérivées sont construites autour de la classe de base, l'instance passe progressivement par la hiérarchie des classes jusqu'à ce qu'elle devienne une instance entièrement construite du type prévu.
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);
Avec ces classes et fonctions membres:
- Dans le bon constructeur, pour
ctd_good
:-
CtorThisBase
est entièrement construit au moment où le constructeurCtorThis
est entré. Par conséquent,s
est dans un état valide lors de l'initialisationi
, et peut donc être consulté. -
i
est initialisé avant quej(this->i)
soit atteint. Par conséquent,i
dans un état valide lors de l'initialisationj
, et peut donc être consulté. -
j
est initialisé avant quek(j)
soit atteint. Par conséquent,j
est dans un état valide lors de l'initialisation dek
, et peut donc être consulté.
-
- Dans le mauvais constructeur, pour
ctd_bad
:-
k
est initialisé après quej(this->k)
est atteint. Par conséquent,k
est dans un état invalide lors de l'initialisationj
, et y accéder entraîne un comportement indéfini. -
CtorThisDerived
n'est construit qu'après la construction deCtorThis
. Par conséquent,b
est dans un état invalide lors de l'initialisation dek
, et l'accès à celui-ci entraîne un comportement indéfini. - L'objet
ctd_bad
est toujours unCtorThis
jusqu'à ce qu'il quitteCtorThis()
, et ne sera pas mis à jour pour utiliser laCtorThisDerived
de CtorThisDerived jusqu'àCtorThisDerived()
queCtorThisDerived()
. Par conséquent,virt_func()
appelleraCtorThis::virt_func()
, qu'il soit destiné à appeler cela ouCtorThisDerived::virt_func()
.
-
Utiliser le pointeur this pour accéder aux données des membres
Dans ce contexte, l'utilisation du pointeur this
n'est pas absolument nécessaire, mais cela rendra votre code plus clair pour le lecteur, en indiquant qu'une fonction ou une variable donnée est un membre de la classe. Un exemple dans cette situation:
// 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;
}
Voyez-le en action ici .
Utiliser le pointeur this pour différencier les données de membre et les paramètres
Ceci est une stratégie très utile pour différencier les données des membres des paramètres ... Prenons l'exemple suivant:
// 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();
}
Vous pouvez voir ici dans le constructeur que nous exécutons ce qui suit:
this->name = name;
Ici, vous pouvez voir que nous associons le nom du paramètre au nom de la variable privée de la classe Dog (this-> name).
Pour voir la sortie du code ci-dessus: http://cpp.sh/75r7
ce Pointer CV-Qualifiers
this
peut aussi être qualifié CV, comme n'importe quel autre pointeur. Cependant, étant donné que this
paramètre n'est pas répertorié dans la liste de paramètres, une syntaxe spéciale est requise pour cela; les qualificateurs cv sont listés après la liste des paramètres, mais avant le corps de la fonction.
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*
};
Comme this
s'agit d'un paramètre, une fonction peut être surchargée en fonction de this
ou de ces qualificateurs cv .
struct CVOverload {
int func() { return 3; }
int func() const { return 33; }
int func() volatile { return 333; }
int func() const volatile { return 3333; }
};
Lorsque c'est this
const
(y compris const volatile
), la fonction est incapable d'écrire à des variables membres à travers elle, que ce soit explicitement ou implicitement. La seule exception à cette règle concerne les variables de membre mutable
, qui peuvent être écrites indépendamment de la constance. De ce fait, const
est utilisé pour indiquer que la fonction membre ne modifie pas l'état logique de l'objet (la façon dont l'objet apparaît dans le monde extérieur), même s'il modifie l'état physique (la façon dont l'objet se présente sous le capot) ).
L'état logique est la manière dont l'objet apparaît aux observateurs extérieurs. Il n'est pas directement lié à l'état physique et peut même ne pas être stocké en tant qu'état physique. Tant que les observateurs extérieurs ne peuvent voir aucun changement, l'état logique est constant, même si vous retournez chaque bit de l'objet.
L'état physique, également appelé état binaire, est la manière dont l'objet est stocké en mémoire. Ceci est le nitty-graffy de l'objet, les 1 et 0 bruts qui composent ses données. Un objet n'est physiquement constant que si sa représentation en mémoire ne change jamais.
Notez que C ++ base la const
sur l'état logique et non sur l'état physique.
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;
}
Notez que si vous pouvez utiliser techniquement const_cast
sur this
pour le rendre non-cv-qualifié, vous avez vraiment, ne devrait vraiment pas, et devrait utiliser mutable
à la place. Un const_cast
est susceptible d'appeler un comportement indéfini lorsqu'il est utilisé sur un objet qui est réellement const
, alors que mutable
est conçu pour être sûr à utiliser. Il est cependant possible que vous rencontriez ceci dans un code extrêmement ancien.
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));
}
};
Cela évite la duplication inutile du code.
Comme avec les pointeurs réguliers, si this
est volatile
(y compris const volatile
), il est chargé depuis la mémoire à chaque fois qu’il est accédé, au lieu d’être mis en cache. Cela a les mêmes effets sur l'optimisation que de déclarer tout autre pointeur volatile
, il faut donc faire attention.
Notez que si une instance est cv-qualifié, les seules fonctions de membres , il est autorisé à accéder sont des fonctions membres dont this
pointeur est au moins aussi cv-qualifié l'instance elle - même:
- Les instances non-cv peuvent accéder à toutes les fonctions membres.
-
const
instancesconst
peuvent accéder aux fonctionsconst
etconst volatile
. -
volatile
instancesvolatile
peuvent accéder aux fonctionsvolatile
etconst volatile
. -
const volatile
instancesconst volatile
peuvent accéder àconst volatile
fonctionsconst volatile
.
C'est l'un des principes clés de la correction des const
.
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.
ce Pointer Ref-Qualifiers
De même pour this
cv-qualificatifs, nous pouvons également appliquer ref-qualificatifs à *this
. Ref-qualificatifs sont utilisés pour choisir entre la sémantique de référence normale et rvalue, ce qui permet au compilateur d'utiliser soit copier ou déplacer la sémantique en fonction qui sont plus appropriés et sont appliqués à *this
lieu de this
.
Notez que malgré les qualificateurs de référence utilisant la syntaxe de référence, this
reste un pointeur. Notez également que les qualificateurs de ref ne modifient pas réellement le type de *this
; il est juste plus facile de décrire et de comprendre leurs effets en les regardant comme s'ils le faisaient.
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
Une fonction membre ne peut pas avoir de surcharge avec et sans qualificatif ref; le programmeur doit choisir entre l'un ou l'autre. Heureusement, les qualificatifs cv peuvent être utilisés en conjonction avec les qualificatifs ref, ce qui permet de respecter les règles d’ exactitude des const
.
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&& {}
};