Pourquoi l'opérateur flèche en C ++ n'est-il pas simplement un alias de *.?

18

En c ++, l'opérateur * peut être surchargé, comme avec un itérateur, mais l'opérateur flèche (->) (. *) Ne fonctionne pas avec les classes qui surchargent l'opérateur *. J'imagine que le préprocesseur pourrait facilement remplacer toutes les instances de -> par (* à gauche) .right, et cela rendrait les itérateurs plus agréables à implémenter. Y a-t-il une raison pratique pour -> d'être différent, ou est-ce juste une particularité de la langue / des concepteurs?

Jakob Weisblat
la source

Réponses:

16

La règle qui foo->barvaut (*foo).barne vaut que pour les opérateurs intégrés.

Unary operator *n'a pas toujours la sémantique de déréférence du pointeur. Je pourrais créer une bibliothèque dans laquelle cela signifie une transposition matricielle, zéro ou plusieurs correspondances d'analyseur, ou à peu près n'importe quoi.

Cela rendrait le langage plus gênant si quelque chose qui surchargeait unaire operator *gagnerait soudainement un que operator ->vous n'avez pas demandé, avec une sémantique qui pourrait ne pas avoir de sens.

operator -> est surchargeable séparément, donc si vous en voulez un, vous pouvez en surcharger un avec un effort minimal.

Notez également qu'une telle surcharge aurait des propriétés plutôt intéressantes, comme le chaînage automatique des operator ->appels jusqu'à ce qu'un membre de la chaîne renvoie un pointeur brut. Ceci est très utile pour les pointeurs intelligents et autres types de proxy.

#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <string>
#include <iostream>
#include <ostream>

struct Foo
{
    boost::shared_ptr<std::string> operator -> () const
    {
        return boost::make_shared<std::string>("trololo");
    }
};

int main()
{
    Foo foo;
    std::cerr << foo->size() << std::endl;
}
Lars Viklund
la source
Qu'est-ce que votre exemple illustre? Renvoyez-vous un pointeur intelligent sur une chaîne et affichez-vous en quelque sorte la taille? Je suis confus.
Trevor Hickey
2
Il illustre le dernier paragraphe de ma réponse, comment utiliser les ->chaînes d'opérateurs jusqu'à ce qu'il obtienne un pointeur brut vers quelque chose, déréférencer et accéder à un membre de celui-ci. Si l'opérateur -> n'a pas enchaîné, l'exemple serait mal formé car un shared_ptr n'est pas un pointeur brut.
Lars Viklund
@LarsViklund: votre réponse a un problème: vous avez dit "opérateur-> ... enchaîne automatiquement l'opérateur-> appelle jusqu'à ce qu'un membre de la chaîne renvoie un pointeur brut". Ce n'est pas correct - en utilisant des A->Bchaînes de syntaxe au plus 1 appel supplémentaire. Ce que fait réellement la syntaxe binaire C ++ -> n'est pas d'appeler opeartor->directement l'objet - au lieu de cela, il regarde le type de Aet vérifie s'il s'agit d'un pointeur brut. Si c'est le ->déréfère et l'exécute B, sinon il appelle l'objet operator->, déréfie le résultat (soit en utilisant un pointeur brut natif ou un autre operator->, puis s'exécute Bsur le résultat
Guss
@Guss: Je ne trouve aucun chapitre et verset pour votre affirmation, ni ne le reproduis dans un compilateur. C ++ 11 13.5.6 / 1 indique que s'il existe une surcharge appropriée, x->mdoit être interprété comme (x.operator->())->m. Si le LHS est quelque chose qui a une surcharge appropriée de operator->nouveau, ce processus se répète jusqu'à ce qu'il y ait juste l' (*x).meffet habituel de 5.2.5 / 2.
Lars Viklund
8

"Le langage de programmation C ++" décrit le fait que ces opérateurs sont différents pour qu'ils puissent l'être, mais dit également:

Si vous fournissez plus d'un de ces opérateurs, il peut être judicieux de fournir l'équivalence, tout comme il est sage de garantir cela ++xet d' x+=1avoir le même effet que x=x+1pour une simple variable xd'une classe si ++, + =, = et + sont fournis.

Il semble donc que les concepteurs de langage aient fourni des points de surcharge distincts, car vous souhaiterez peut -être les surcharger différemment, plutôt que de supposer que vous souhaitez toujours qu'ils soient identiques.


la source
7

En règle générale, C ++ est conçu pour favoriser la flexibilité, donc les surcharges *et ->sont distinctes. Bien qu'il soit assez inhabituel de le faire, si vous le voulez assez, vous pouvez écrire ces surcharges pour faire des choses entièrement différentes (par exemple, cela pourrait avoir du sens pour un langage spécifique au domaine implémenté à l'intérieur de C ++).

Cela dit, les itérateurs prennent en charge l'une ou l'autre utilisation. Sur les anciennes implémentations, vous pourriez trouver une bibliothèque qui nécessite (*iter).whateverau lieu de iter->whatever, mais si c'est le cas, c'est un bogue dans l'implémentation, pas une caractéristique du langage. Compte tenu de la quantité de travail impliquée dans la mise en œuvre de tous les conteneurs / algorithmes / itérateurs standard, il n'est pas surprenant que certaines versions préliminaires aient été quelque peu incomplètes, mais elles n'ont jamais vraiment été conçues de cette façon.

Jerry Coffin
la source
Je ne savais pas que les conteneurs de bibliothèque standard étaient implémentés ->, ni qu'il était surchargeable.
Jakob Weisblat
3
C ++ 03 24.1 / 1 requiert que tout itérateur où (*i).mest valide doit prendre i->men charge avec la même sémantique.
Lars Viklund