Surcharger l'opérateur -> en C++
by Baduit
Qu’est-ce que l’opérateur -> ?
L’opérateur ->, de son petit nom opérateur membre de pointeur, est un des opérateurs d’accès membres. De base, il permet d’accéder à un attribut ou à une méthode d’un objet via un pointeur. Exemple :
1
2
3
std::string str = "Hello, World !";
std::string* ptr = &str;
bool is_empty = ptr->empty();
Il est l’équivalent de la combinaison de l’opérateur de déréférencement “*” et de l’opérateur membre d’objet “.” , par exemple le snippet de code précédent est l’équivalent de celui-ci :
1
2
3
4
std::string str = "Hello, World !";
std::string* ptr = &str;
// On utilise des parenthèses à cause de la précédence de l'opérateur . sur l'opérateur *
bool is_empty = (*ptr).empty();
La surcharge, ça se passe comment ?
Le prototype de la surcharge
Pour surcharger l’opérateur ->, il faut déclarer une méthode nommée operator->, sans argument, qui retourne soit :
- un pointeur
- un objet, par référence ou par valeur, qui a lui-même une surcharge de l’opérateur ->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Exemple où l'on retourne un pointeur
struct StrWrapper
{
std::string* operator->()
{
return &str;
}
std::string str;
};
// Exemple où l'on retourne un objet qui a un opérateur -> surchargé
struct WrapperOfWrapper
{
StrWrapper operator->()
{
return str_wrapper;
}
StrWrapper str_wrapper;
};
Le comportement si un pointeur est retourné
C’est le cas le plus simple : dans ce cas, on utilise directement le pointeur retourné comme un pointeur classique, et ainsi on peut accéder à un attribut ou à une méthode comme on le ferait avec un pointeur normal. Si on reprend la classe StrWrapper de l’exemple ci-dessus, on peut écrire ce code-là :
1
2
3
4
5
StrWrapper str_wrapper{"Awesome"};
std::cout << str_wrapper->size() << std::endl; // Affiche 7
// La ligne ci-dessus et l'équivalent de la ligne ci-dessous car l'opérateur ->
// retourne un pointeur vers la std::string contenu dans la classe
std::cout << str_wrapper.str.size() << std::endl; // Affiche 7 aussi
Le comportement si un objet est retourné
Dans ce cas, le compilateur va appeler l’opérateur -> de l’objet retourné : si ce nouvel appel retourne un objet, on recommence successivement jusqu’à obtenir un pointeur, ce qui nous fait retomber sur le premier cas expliqué précédemment. Voici un exemple reprenant les classes StrWrapper et WrapperOfWrapper pour mieux comprendre :
1
2
3
4
5
6
WrapperOfWrapper wow{"Awesome"};
std::cout << wow->size() << std::endl; // Affiche 7
// C'est l'équivalent de ça :
std::cout << wow.str_wrapper->size() << std::endl;
// Qui est lui même l'équivalent de ça :
std::cout << wow.str_wrapper.str.size() << std::endl;
Pourquoi surcharger cet opérateur ?
On peut citer deux raisons communes de surcharger l’opérateur -> :
- Avoir une utilisation et un comportement similaire à celui d’un pointeur : c’est le cas par exemple pour les pointeurs intelligents
- Pouvoir accéder facilement aux méthodes et attributs d’une classe wrappée à partir d’une autre classe (qui la contient). On peut citer les itérateurs de la bibliothèque standard comme exemple.
Exemples de surcharges de l’opérateur -> dans la bibliothèque standard
- std::unique_ptr
- std::shared_ptr
- std::optional
- Les itérateurs des conteneurs
Sources
- https://docs.microsoft.com/en-us/cpp/cpp/smart-pointers-modern-cpp?view=msvc-160
- https://en.cppreference.com/w/cpp/language/operator_member_access
- https://en.cppreference.com/w/cpp/language/operators
- https://en.cppreference.com/w/cpp/utility/optional
- https://en.cppreference.com/w/cpp/memory/unique_ptr
- https://en.cppreference.com/w/cpp/memory/shared_ptr