Il y a beaucoup de messages qui se plaignent de la surcharge des opérateurs.
J'ai senti que je devais clarifier les concepts de «surcharge de l'opérateur», en offrant un point de vue alternatif sur ce concept.
Code obscurcissant?
Cet argument est une erreur.
L'obscurcissement est possible dans toutes les langues ...
Il est aussi facile d'obscurcir le code en C ou Java via des fonctions / méthodes qu'en C ++ via des surcharges d'opérateurs:
// C++
T operator + (const T & a, const T & b) // add ?
{
T c ;
c.value = a.value - b.value ; // subtract !!!
return c ;
}
// Java
static T add (T a, T b) // add ?
{
T c = new T() ;
c.value = a.value - b.value ; // subtract !!!
return c ;
}
/* C */
T add (T a, T b) /* add ? */
{
T c ;
c.value = a.value - b.value ; /* subtract !!! */
return c ;
}
... même dans les interfaces standard de Java
Pour un autre exemple, voyons l' Cloneable
interface en Java:
Vous êtes censé cloner l'objet implémentant cette interface. Mais tu pourrais mentir. Et créez un objet différent. En fait, cette interface est si faible que vous pourriez renvoyer un autre type d'objet, juste pour le plaisir:
class MySincereHandShake implements Cloneable
{
public Object clone()
{
return new MyVengefulKickInYourHead() ;
}
}
Comme le Cloneable
interface peut être utilisée abusivement / obscurcie, devrait-elle être interdite pour les mêmes motifs que la surcharge de l'opérateur C ++ est censée être?
Nous pourrions surcharger la toString()
méthode d'une MyComplexNumber
classe pour qu'elle retourne l'heure stringifiée de la journée. La toString()
surcharge devrait-elle également être interdite? On pourrait saboter MyComplexNumber.equals
pour lui faire retourner une valeur aléatoire, modifier les opérandes ... etc etc etc ..
En Java, comme en C ++, ou dans n'importe quel langage, le programmeur doit respecter un minimum de sémantique lors de l'écriture de code. Cela signifie implémenter une add
fonction qui ajoute, et une Cloneable
méthode d'implémentation qui clone, et un++
opérateur qui incrémente.
Qu'est-ce qui obscurcit de toute façon?
Maintenant que nous savons que le code peut être saboté même à travers les méthodes Java immaculées, nous pouvons nous interroger sur l'utilisation réelle de la surcharge d'opérateur en C ++?
Notation claire et naturelle: méthodes vs surcharge de l'opérateur?
Nous comparerons ci-dessous, pour différents cas, le "même" code en Java et C ++, pour avoir une idée du type de style de codage le plus clair.
Comparaisons naturelles:
// C++ comparison for built-ins and user-defined types
bool isEqual = A == B ;
bool isNotEqual = A != B ;
bool isLesser = A < B ;
bool isLesserOrEqual = A <= B ;
// Java comparison for user-defined types
boolean isEqual = A.equals(B) ;
boolean isNotEqual = ! A.equals(B) ;
boolean isLesser = A.comparesTo(B) < 0 ;
boolean isLesserOrEqual = A.comparesTo(B) <= 0 ;
Veuillez noter que A et B peuvent être de tout type en C ++, tant que les surcharges d'opérateur sont fournies. En Java, lorsque A et B ne sont pas des primitives, le code peut devenir très déroutant, même pour des objets de type primitif (BigInteger, etc.) ...
Accesseurs naturels de tableau / conteneur et indice:
// C++ container accessors, more natural
value = myArray[25] ; // subscript operator
value = myVector[25] ; // subscript operator
value = myString[25] ; // subscript operator
value = myMap["25"] ; // subscript operator
myArray[25] = value ; // subscript operator
myVector[25] = value ; // subscript operator
myString[25] = value ; // subscript operator
myMap["25"] = value ; // subscript operator
// Java container accessors, each one has its special notation
value = myArray[25] ; // subscript operator
value = myVector.get(25) ; // method get
value = myString.charAt(25) ; // method charAt
value = myMap.get("25") ; // method get
myArray[25] = value ; // subscript operator
myVector.set(25, value) ; // method set
myMap.put("25", value) ; // method put
En Java, nous voyons que pour que chaque conteneur fasse la même chose (accéder à son contenu via un index ou un identifiant), nous avons une manière différente de le faire, ce qui est déroutant.
En C ++, chaque conteneur utilise de la même manière pour accéder à son contenu, grâce à la surcharge de l'opérateur.
Manipulation naturelle des types avancés
Les exemples ci-dessous utilisent un Matrix
objet, trouvé en utilisant les premiers liens trouvés sur Google pour " objet Matrix Java " et " objet Matrix C ++ ":
// C++ YMatrix matrix implementation on CodeProject
// http://www.codeproject.com/KB/architecture/ymatrix.aspx
// A, B, C, D, E, F are Matrix objects;
E = A * (B / 2) ;
E += (A - B) * (C + D) ;
F = E ; // deep copy of the matrix
// Java JAMA matrix implementation (seriously...)
// http://math.nist.gov/javanumerics/jama/doc/
// A, B, C, D, E, F are Matrix objects;
E = A.times(B.times(0.5)) ;
E.plusEquals(A.minus(B).times(C.plus(D))) ;
F = E.copy() ; // deep copy of the matrix
Et cela ne se limite pas aux matrices. Le BigInteger
etBigDecimal
classes de Java souffrent de la même verbosité déroutante, tandis que leurs équivalents en C ++ sont aussi clairs que les types intégrés.
Itérateurs naturels:
// C++ Random Access iterators
++it ; // move to the next item
--it ; // move to the previous item
it += 5 ; // move to the next 5th item (random access)
value = *it ; // gets the value of the current item
*it = 3.1415 ; // sets the value 3.1415 to the current item
(*it).foo() ; // call method foo() of the current item
// Java ListIterator<E> "bi-directional" iterators
value = it.next() ; // move to the next item & return the value
value = it.previous() ; // move to the previous item & return the value
it.set(3.1415) ; // sets the value 3.1415 to the current item
Foncteurs naturels:
// C++ Functors
myFunctorObject("Hello World", 42) ;
// Java Functors ???
myFunctorObject.execute("Hello World", 42) ;
Concaténation de texte:
// C++ stream handling (with the << operator)
stringStream << "Hello " << 25 << " World" ;
fileStream << "Hello " << 25 << " World" ;
outputStream << "Hello " << 25 << " World" ;
networkStream << "Hello " << 25 << " World" ;
anythingThatOverloadsShiftOperator << "Hello " << 25 << " World" ;
// Java concatenation
myStringBuffer.append("Hello ").append(25).append(" World") ;
Ok, en Java, vous pouvez utiliser MyString = "Hello " + 25 + " World" ;
aussi ... Mais, attendez une seconde: c'est la surcharge de l'opérateur, n'est-ce pas? N'est-ce pas de la triche ???
:-RÉ
Code générique?
Les mêmes opérandes de modification de code générique doivent être utilisables à la fois pour les objets intégrés / primitifs (qui n'ont pas d'interface en Java), les objets standard (qui ne peuvent pas avoir la bonne interface) et les objets définis par l'utilisateur.
Par exemple, calculer la valeur moyenne de deux valeurs de types arbitraires:
// C++ primitive/advanced types
template<typename T>
T getAverage(const T & p_lhs, const T & p_rhs)
{
return (p_lhs + p_rhs) / 2 ;
}
int intValue = getAverage(25, 42) ;
double doubleValue = getAverage(25.25, 42.42) ;
complex complexValue = getAverage(cA, cB) ; // cA, cB are complex
Matrix matrixValue = getAverage(mA, mB) ; // mA, mB are Matrix
// Java primitive/advanced types
// It won't really work in Java, even with generics. Sorry.
Discuter de la surcharge de l'opérateur
Maintenant que nous avons vu des comparaisons équitables entre le code C ++ utilisant la surcharge d'opérateur et le même code en Java, nous pouvons maintenant discuter de la "surcharge d'opérateur" en tant que concept.
La surcharge des opérateurs existait depuis avant les ordinateurs
Même en dehors de la science informatique, il y a surcharge de l' opérateur: Par exemple, en mathématiques, des opérateurs comme +
, -
, *
, etc. sont surchargés.
En effet, la signification +
, -
, *
, etc. change en fonction des types des opérandes (Numerics, vecteurs, fonctions d'onde quantique, matrices, etc.).
La plupart d'entre nous, dans le cadre de nos cours de sciences, ont appris plusieurs significations pour les opérateurs, selon les types d'opérandes. Les avons-nous trouvés déroutants, eux?
La surcharge de l'opérateur dépend de ses opérandes
C'est la partie la plus importante de la surcharge des opérateurs: comme en mathématiques ou en physique, l'opération dépend du type de ses opérandes.
Donc, connaissez le type de l'opérande, et vous connaîtrez l'effet de l'opération.
Même C et Java ont une surcharge d'opérateur (codée en dur)
En C, le comportement réel d'un opérateur changera en fonction de ses opérandes. Par exemple, l'ajout de deux entiers est différent de l'ajout de deux doubles, ou même d'un entier et d'un double. Il y a même tout le domaine arithmétique du pointeur (sans transtypage, vous pouvez ajouter à un pointeur un entier, mais vous ne pouvez pas ajouter deux pointeurs ...).
En Java, il n'y a pas d'arithmétique de pointeur, mais quelqu'un a quand même trouvé que la concaténation de chaînes sans l' +
opérateur serait assez ridicule pour justifier une exception dans le credo "la surcharge de l'opérateur est un mal".
C'est juste que vous, en tant que codeur C (pour des raisons historiques) ou Java (pour des raisons personnelles , voir ci-dessous), vous ne pouvez pas fournir le vôtre.
En C ++, la surcharge d'opérateur n'est pas facultative ...
En C ++, la surcharge d'opérateur pour les types intégrés n'est pas possible (et c'est une bonne chose), mais les types définis par l' utilisateur peuvent avoir définis par l'utilisateur surcharges d'opérateur .
Comme déjà dit précédemment, en C ++, et contrairement à Java, les types d'utilisateurs ne sont pas considérés comme des citoyens de seconde classe du langage, par rapport aux types intégrés. Donc, si les types intégrés ont des opérateurs, les types d'utilisateurs devraient également pouvoir les avoir.
La vérité est que, comme toString()
, clone()
, les equals()
méthodes sont pour Java ( par exemple quasi-standard comme ), la surcharge de l' opérateur de C est tellement partie de C ++ qu'il devient aussi naturel que les opérateurs d' origine C, ou avant des méthodes Java mentionnées.
Combinée à la programmation de modèles, la surcharge de l'opérateur devient un modèle de conception bien connu. En fait, vous ne pouvez pas aller très loin dans STL sans utiliser des opérateurs surchargés et des opérateurs de surcharge pour votre propre classe.
... mais il ne faut pas en abuser
La surcharge de l'opérateur doit s'efforcer de respecter la sémantique de l'opérateur. Ne pas soustraire dans un +
opérateur (comme dans "ne pas soustraire dans une add
fonction", ou "renvoyer des conneries dans une clone
méthode").
La surcharge de plâtre peut être très dangereuse car elle peut conduire à des ambiguïtés. Ils devraient donc vraiment être réservés à des cas bien définis. En ce qui concerne &&
et ||
, ne savent jamais les surcharger , sauf si vous vraiment ce que vous faites, que vous perdrez l'évaluation de court-circuit que les opérateurs natifs &&
et ||
profiter.
Alors ... Ok ... Alors pourquoi ce n'est pas possible en Java?
Parce que James Gosling l'a dit:
J'ai omis la surcharge d'opérateur comme un choix assez personnel parce que j'avais vu trop de gens en abuser en C ++.
James Gosling. Source: http://www.gotw.ca/publications/c_family_interview.htm
Veuillez comparer le texte de Gosling ci-dessus avec Stroustrup ci-dessous:
De nombreuses décisions de conception C ++ ont leurs racines dans mon aversion pour forcer les gens à faire des choses d'une manière particulière [...] Souvent, j'ai été tenté de proscrire une fonctionnalité que je n'aimais pas personnellement, je me suis abstenu de le faire parce que je ne pensais pas que j'avais le droit de forcer mes vues sur les autres .
Bjarne Stroustrup. Source: La conception et l'évolution de C ++ (1.3 Contexte général)
La surcharge de l'opérateur serait-elle avantageuse pour Java?
Certains objets bénéficieraient grandement de la surcharge des opérateurs (types concrets ou numériques, comme BigDecimal, nombres complexes, matrices, conteneurs, itérateurs, comparateurs, analyseurs, etc.).
En C ++, vous pouvez profiter de cet avantage en raison de l'humilité de Stroustrup. En Java, vous êtes simplement foutu à cause du choix personnel de Gosling .
Pourrait-il être ajouté à Java?
Les raisons de ne pas ajouter de surcharge d'opérateur maintenant à Java pourraient être un mélange de politique interne, d'allergie à la fonctionnalité, de méfiance envers les développeurs (vous savez, les saboteurs qui semblent hanter les équipes Java ...), la compatibilité avec les JVM précédentes, le temps d'écrire une spécification correcte, etc.
Alors ne retenez pas votre souffle en attendant cette fonctionnalité ...
Mais ils le font en C # !!!
Ouais...
Si c'est loin d'être la seule différence entre les deux langues, celle-ci ne manque jamais de m'amuser.
Apparemment, les gens C #, avec leur "chaque primitive est un struct
, et un struct
dérive d'Object" , ont bien fait du premier coup.
Malgré tous les FUD contre la surcharge d'opérateur définie utilisée, les langages suivants le prennent en charge: Scala , Dart , Python , F # , C # , D , Algol 68 , Smalltalk , Groovy , Perl 6 , C ++, Ruby , Haskell , MATLAB , Eiffel , Lua , Clojure , Fortran 90 , Swift , Ada , Delphi 2005 ...
Tant de langues, avec tant de philosophies différentes (et parfois opposées), et pourtant elles sont toutes d'accord sur ce point.
Nourriture pour la pensée...