Je viens de lire un des articles de Joel dans lequel il dit:
En général, je dois admettre que j'ai un peu peur des fonctionnalités du langage qui cachent des choses . Quand vous voyez le code
i = j * 5;
… En C, vous savez au moins que j est multiplié par cinq et que les résultats sont stockés dans i.
Mais si vous voyez le même extrait de code en C ++, vous ne savez rien. Rien. La seule façon de savoir ce qui se passe réellement en C ++ est de savoir ce que sont les types i et j, ce qui pourrait être déclaré ailleurs. C'est parce que j est peut-être d'un type
operator*
surchargé et qu'il fait quelque chose de terriblement spirituel lorsque vous essayez de le multiplier.
(C'est moi qui souligne.) Peur des fonctionnalités linguistiques qui cachent des choses? Comment pouvez-vous avoir peur de cela? Cacher des objets (également appelés abstraction ) n’est-il pas l’ une des idées clés de la programmation orientée objet? Chaque fois que vous appelez une méthode a.foo(b)
, vous n'avez aucune idée de ce que cela pourrait faire. Vous devez trouver quels types a
et quels types b
sont quelque chose qui pourrait être déclaré ailleurs. Alors devrions-nous nous débarrasser de la programmation orientée objet, car elle cache trop de choses au programmeur?
Et en quoi est-il j * 5
différent de j.multiply(5)
ce que vous pourriez avoir à écrire dans un langage qui ne prend pas en charge la surcharge des opérateurs? Encore une fois, il vous faudrait connaître le type de méthode j
et jeter un coup d'œil à l'intérieur de la multiply
méthode, car bon à savoir, il j
pourrait s'agir d'un type qui possède une multiply
méthode qui fait quelque chose de terriblement spirituel.
"Muahaha, je suis un mauvais programmeur qui nomme une méthode multiply
, mais ce qu'elle fait est totalement obscure et non intuitive et n'a absolument rien à faire avec la multiplication des choses." Est-ce un scénario que nous devons prendre en compte lors de la conception d'un langage de programmation? Ensuite, nous devons abandonner les identifiants des langages de programmation sous prétexte qu’ils pourraient être trompeurs!
Si vous voulez savoir ce que fait une méthode, vous pouvez regarder la documentation ou jeter un coup d'œil à l'intérieur de la mise en œuvre. La surcharge d'opérateur n'est que du sucre syntaxique, et je ne vois pas du tout en quoi cela change le jeu.
S'il te plaît, éclaire-moi.
Réponses:
L'abstraction "cache" le code afin que vous n'ayez pas à vous soucier du fonctionnement interne et que vous ne puissiez souvent pas le modifier, mais l'intention n'était pas de vous empêcher de le regarder. Nous faisons simplement des suppositions sur les opérateurs et, comme Joel l'a dit, cela pourrait être n'importe où. Avoir une fonction de programmation exigeant que tous les opérateurs surchargés soient établis dans un emplacement spécifique peut aider à la trouver, mais je ne suis pas sûr que cela en facilite l'utilisation.
Je ne vois pas comment faire quelque chose qui ne ressemble pas beaucoup à la multiplication, mieux qu'une fonction appelée Get_Some_Data qui supprime des données.
la source
<<
opérateur défini sur les flux qui n'a rien à voir avec le décalage au niveau du bit, directement dans la bibliothèque standard de C ++.IMHO, les fonctionnalités du langage telles que la surcharge des opérateurs donnent plus de puissance au programmeur. Et, comme nous le savons tous, un grand pouvoir entraîne de grandes responsabilités. Les fonctionnalités qui vous donnent plus de puissance vous donnent également plus de moyens de vous tirer une balle dans le pied et, évidemment, elles devraient être utilisées judicieusement.
Par exemple, il est parfaitement logique de surcharger le
+
ou l’*
opérateur pourclass Matrix
ouclass Complex
. Tout le monde saura instantanément ce que cela signifie. Par contre, le fait que cela+
signifie concaténation de chaînes de caractères n’est pas évident du tout, même si Java le fait en tant que partie du langage et STL le fait pourstd::string
utiliser la surcharge d’opérateurs.Un autre bon exemple de la surcharge d’opérateurs qui rend le code plus clair est les pointeurs intelligents en C ++. Vous voulez que les pointeurs intelligents se comportent autant que possible comme des pointeurs normaux. Il est donc parfaitement logique de surcharger le unaire
*
et les->
opérateurs.En substance, la surcharge des opérateurs n’est rien d’autre qu’un autre moyen de nommer une fonction. Et il existe une règle pour nommer les fonctions: le nom doit être descriptif, rendant immédiatement évident le travail de la fonction. La même règle exacte s'applique à la surcharge de l'opérateur.
la source
*
pour ceux-ci peut être source de confusion. On pourrait dire que vous pourriez utiliseroperator*()
pour le produit scalaire etoperator%()
le produit croisé, mais je ne le ferais pas pour une bibliothèque à usage général.A-B
comme telB-A
, et tous les opérateurs suivent ce modèle. Il y a toujours une exception: lorsque le compilateur peut prouver que ce n'est pas grave, il est permis de tout réorganiser.En Haskell, "+", "-", "*", "/" etc ne sont que des fonctions (infixes).
Devriez-vous nommer une fonction infixe "plus" comme dans "4 plus 2"? Pourquoi pas, si addition est ce que votre fonction fait. Devriez-vous nommer votre fonction "plus" "+"? Pourquoi pas.
Je pense que le problème avec les soi-disant "opérateurs" est qu’ils ressemblent pour la plupart à des opérations mathématiques et qu’il n’ya pas beaucoup de façons de les interpréter. Il y a donc de grandes attentes quant à ce que fait une telle méthode / fonction / opérateur.
EDIT: fait mon point plus clair
la source
int
,float
,long long
et que ce soit. Alors, de quoi s'agit-il?+
pour différents types de numéros intégrés, mais à la création de surcharges définies par l'utilisateur. d'où mon commentaire.Sur la base des autres réponses que j'ai vues, je ne peux que conclure que la véritable objection à la surcharge de l'opérateur est le désir de code immédiatement évident.
C'est tragique pour deux raisons:
la source
Je suis un peu d'accord.
Si vous écrivez
multiply(j,5)
, celaj
peut être de type scalaire ou matriciel, rendantmultiply()
plus ou moins complexe, selon ce quij
est. Toutefois, si vous abandonnez complètement l’idée de surcharger, la fonction doit être nomméemultiply_scalar()
oumultiply_matrix()
indiquer clairement ce qui se passe en dessous.Il y a un code où beaucoup d'entre nous préféreraient une méthode et un code où la plupart d'entre nous préférerions une méthode différente. La plupart du code, cependant, se situe entre les deux extrêmes. Ce que vous préférez là-bas dépend de vos antécédents et de vos préférences personnelles.
la source
multiply_matrix()
n’apprécieront pas non plus la programmation générique.real_multiply()
- être à peu près . Les développeurs ne sont souvent pas doués pour les noms, etoperator*()
au moins vont être cohérents.operator*()
pourrions faire quelque chose de stupide,j
est une macro évaluant des expressions impliquant cinq appels de fonction, et tout le reste. Vous ne pouvez alors plus comparer les deux approches. Mais, oui, il est difficile de nommer les choses, même si cela en vaut la peine.Je vois deux problèmes avec la surcharge de l'opérateur.
&&
,||
ou,
, vous perdez les points de séquence impliqués par les variantes intégrées de ces opérateurs (ainsi que le comportement de court-circuit des opérateurs logiques). Pour cette raison, il est préférable de ne pas surcharger ces opérateurs, même si le langage le permet.operator<<
pour les flux.la source
&&
et||
sans impliquer de séquençage était une grosse erreur (IMHO, si C ++ devait autoriser la surcharge de ceux-ci, il aurait dû utiliser un format spécial "à deux fonctions", la première fonction étant requise pour renvoyer un type implicitement convertible en un entier, la deuxième fonction peut prendre deux ou trois arguments, l'argument "extra" de la deuxième fonction étant le type de retour du premier. Le compilateur appelle alors la première fonction. si elle renvoie non nulle, évalue le deuxième opérande et appelle la deuxième fonction.)foo.bar[3].X
d’être gérée parfoo
la classe, plutôt quefoo
d’exposer un membre qui pourrait supporter la souscription et ensuite exposer un membreX
. Si on voulait forcer l'évaluation via l'accès réel des membres, on écrirait((foo.bar)[3]).X
.D'après mon expérience personnelle, la façon dont Java permet d'utiliser plusieurs méthodes, sans surcharger l'opérateur, signifie que chaque fois que vous voyez un opérateur, vous savez exactement ce qu'il fait.
Vous n'avez pas à savoir si vous
*
appelez du code étrange, mais sachez qu'il s'agit d'une multiplication et que son comportement est identique à celui défini par la spécification du langage Java. Cela signifie que vous pouvez vous concentrer sur le comportement réel au lieu de découvrir tous les éléments de guichet définis par le programmeur.En d'autres termes, interdire la surcharge de l'opérateur est un avantage pour le lecteur , et non pour l' écrivain , et facilite donc la maintenance des programmes!
la source
list.get(n)
syntaxe?std::list
ne surcharge pasoperator[]
(ni ne donne aucun autre moyen d'indexation dans la liste), car une telle opération serait O (n), et une interface de liste ne devrait pas exposer une telle fonction si vous vous souciez de l'efficacité. Les clients peuvent être tentés de parcourir des listes chaînées avec des index, rendant O (n) algorithmes inutilement O (n ^ 2). Vous le voyez assez souvent dans le code Java, surtout si les gens travaillent avec l'List
interface qui vise à faire abstraction de la complexité.time.add(anotherTime)
, vous devrez également vérifier si le programmeur de la bibliothèque a implémenté l'opération d'ajout "correctement" (peu importe ce que cela signifie).Une différence entre la surcharge
a * b
et l’appelmultiply(a,b)
est que cette dernière peut facilement être recherchée. Si lamultiply
fonction n'est pas surchargée pour différents types, vous pouvez savoir exactement ce que la fonction va faire, sans avoir à suivre les types dea
etb
.Linus Torvalds a un argument intéressant à propos de la surcharge des opérateurs. Dans quelque chose comme le développement du noyau Linux, où la plupart des modifications sont envoyées via des correctifs par courrier électronique, il est important que les responsables puissent comprendre le fonctionnement d'un correctif avec seulement quelques lignes de contexte autour de chaque modification. Si les fonctions et les opérateurs ne sont pas surchargés, le correctif peut être plus facilement lu de manière indépendante du contexte, car vous n'avez pas à parcourir le fichier modifié pour déterminer quels sont tous les types et pour rechercher les opérateurs surchargés.
la source
Je suppose que cela a quelque chose à voir avec le dépassement des attentes. Je suis habitué au C ++, au comportement de l'opérateur qui n'est pas entièrement dicté par le langage, et vous ne serez pas surpris qu'un opérateur fasse quelque chose de bizarre. Si vous êtes habitué à des langages qui ne possèdent pas cette fonctionnalité et que vous voyez ensuite du code C ++, vous apportez les attentes de ces autres langages et vous risquez d'être très surpris lorsque vous découvrez qu'un opérateur surchargé fait quelque chose de génial.
Personnellement, je pense qu'il y a une différence. Lorsque vous pouvez modifier le comportement de la syntaxe intégrée du langage, il devient plus opaque de raisonner. Les langages qui n'autorisent pas la méta-programmation sont syntaxiquement moins puissants, mais conceptuellement plus simples à comprendre.
la source
Je pense que la surcharge des opérateurs mathématiques n’est pas le véritable problème de la surcharge des opérateurs en C ++. Je pense que surcharger les opérateurs qui ne devraient pas s'appuyer sur le contexte de l'expression (c'est-à-dire du type) est "pervers". Par exemple, la surcharge
,
[ ]
( )
->
->*
new
delete
ou même le unaire*
. Vous avez certaines attentes de la part de ces opérateurs qui ne devraient jamais changer.la source
operator[]
, des foncteurs sansoperator()
, des pointeurs intelligents sansoperator->
et ainsi de suite.[]
devrait toujours être un accesseur semblable à un tableau, et->
devrait toujours signifier accéder à un membre. Peu importe qu'il s'agisse d'un tableau ou d'un conteneur différent, ou d'un pointeur intelligent ou non.Je comprends parfaitement que vous n'aimiez pas l'argument de Joël à propos de la dissimulation. Moi non plus. Il est en effet bien préférable d’utiliser le signe "+" pour les types tels que les types numériques intégrés ou pour vos propres types comme, par exemple, la matrice. J'admets que c'est soigné et élégant de pouvoir multiplier deux matrices avec le '*' au lieu de '.multiply ()'. Et après tout, nous avons le même genre d'abstraction dans les deux cas.
Ce qui fait mal ici, c'est la lisibilité de votre code. Dans la vie réelle, pas dans l'exemple académique de la multiplication matricielle. Surtout si votre langue permet de définir des opérateurs qui ne sont pas initialement présents dans le noyau de la langue, par exemple
=:=
. Beaucoup de questions supplémentaires se posent à ce stade. De quoi parle ce maudit opérateur? Je veux dire quelle est la préséance de cette chose? Qu'est-ce que l'associativité? Dans quel ordre lea =:= b =:= c
vraiment exécuté?C'est déjà un argument contre la surcharge de l'opérateur. Toujours pas convaincu? Vérifier les règles de priorité ne vous a pas pris plus de 10 secondes? Ok, allons plus loin.
Si vous commencez à utiliser un langage qui autorise la surcharge d'opérateurs, par exemple le langage populaire dont le nom commence par «S», vous apprendrez rapidement que les concepteurs de bibliothèques adorent remplacer les opérateurs. Bien sûr, ils sont bien éduqués, ils suivent les meilleures pratiques (pas de cynisme ici) et toutes leurs API sont parfaitement logiques quand on les regarde séparément.
Maintenant, imaginez que vous deviez utiliser quelques API qui font un usage intensif des opérateurs surchargeant ensemble dans un code unique. Ou mieux encore, vous devez lire un code hérité comme celui-là. C'est à ce moment que la surcharge de l'opérateur est vraiment nulle. En gros, s’il ya beaucoup d’opérateurs surchargés à un endroit donné, ils commenceront bientôt à se mêler aux autres caractères non alphanumériques de votre code de programme. Ils se mêleront à des caractères non alphanumériques qui ne sont pas réellement des opérateurs, mais plutôt des éléments de grammaire linguistique plus fondamentaux qui définissent des éléments tels que des blocs et des portées, des instructions de contrôle de flux de formes ou des méta-choses. Vous devrez mettre les lunettes et rapprocher vos yeux de 10 cm de l'écran LCD pour comprendre ce désordre visuel.
la source
En général, j'évite d'utiliser la surcharge de l'opérateur de manière non intuitive. Autrement dit, si j'ai une classe numérique, la surcharge * est acceptable (et encouragée). Cependant, si j'ai un employé de classe, que ferait une surcharge *? En d’autres termes, surchargez les opérateurs de manière intuitive qui facilitent la lecture et la compréhension.
Acceptable / Encouragé:
Pas acceptable:
la source
Outre ce qui a déjà été dit ici, il existe un autre argument contre la surcharge des opérateurs. En effet, si vous écrivez
+
, il est évident que vous entendez ajouter quelque chose à quelque chose. Mais ce n'est pas toujours le cas.C ++ lui-même fournit un excellent exemple d'un tel cas. Comment est
stream << 1
censé être lu? flux décalé à gauche de 1? Ce n'est pas évident du tout, sauf si vous savez explicitement que << en C ++ écrit également dans le flux. Cependant, si cette opération était implémentée en tant que méthode, aucun développeur sensé n'écriraito.leftShift(1)
, ce serait quelque chose commeo.write(1)
.L'essentiel est qu'en rendant la surcharge d'opérateur indisponible, le langage incite les programmeurs à réfléchir aux noms des opérations. Même si le nom choisi n'est pas parfait, il est toujours plus difficile d'interpréter mal un nom qu'un signe.
la source
Par rapport aux méthodes expliquées, les opérateurs sont plus courts, mais ils n’ont pas besoin de parenthèses. Les parenthèses sont relativement peu pratiques à taper. Et vous devez les équilibrer. Au total, tout appel de méthode nécessite trois caractères de bruit en clair par rapport à un opérateur. Cela rend l'utilisation des opérateurs très, très tentante.
Pourquoi d'autre voudrait-on à ceci
cout << "Hello world"
:?Le problème avec la surcharge est que la plupart des programmeurs sont incroyablement paresseux et que la plupart des programmeurs ne peuvent pas se permettre de l'être.
Ce qui pousse les programmeurs C ++ à l’abus de la surcharge d’opérateurs n’est pas sa présence, mais l’absence d’un moyen plus rationnel d’exécuter des appels de méthodes. Et les gens n'ont pas simplement peur de la surcharge des opérateurs parce que c'est possible, mais parce que c'est fait.
Notez que, par exemple, dans Ruby et Scala, personne n’a peur de la surcharge de l’opérateur. Outre le fait que l'utilisation des opérateurs n'est pas vraiment plus courte que les méthodes, une autre raison est que Ruby limite la surcharge des opérateurs à un minimum raisonnable, alors que Scala vous permet de déclarer vos propres opérateurs , rendant ainsi éviter les collisions triviales.
la source
La raison pour laquelle la surcharge d'opérateur est effrayante, c'est parce qu'il y a un grand nombre de programmeurs qui ne penseraient jamais que
*
cela ne signifie pas simplement "multiplier", alors qu'une méthode commefoo.multiply(bar)
au moins instantanément indique à ce programmeur que quelqu'un a écrit une méthode de multiplication personnalisée . À quel moment ils se demanderaient pourquoi et partiraient à la recherche.J'ai travaillé avec de "bons programmeurs" qui occupaient des positions élevées pour créer des méthodes appelées "CompareValues" qui prendraient 2 arguments, appliqueraient les valeurs de l'une à l'autre et renverraient un booléen. Ou une méthode appelée "LoadTheValues" qui irait dans la base de données pour 3 autres objets, obtenir des valeurs, faire des calculs, la modifier
this
et la sauvegarder dans la base de données.Si je travaille dans une équipe avec ces types de programmeurs, je sais instantanément qu’il faut enquêter sur les choses sur lesquelles ils ont travaillé. S'ils surchargent un opérateur, je n'ai absolument aucun moyen de savoir qu'ils l'ont fait, sauf de supposer qu'ils l'ont fait et d'aller les chercher.
Dans un monde parfait, ou avec une équipe de programmeurs parfaits, la surcharge d'opérateurs est probablement un outil fantastique. Je n'ai pas encore travaillé sur une équipe de programmeurs parfaits, c'est pourquoi c'est effrayant.
la source