Il y a beaucoup de messages se plaignant de la surcharge des opérateurs.
J'ai senti que je devais clarifier les concepts de "surcharge d'opérateurs", en offrant un point de vue alternatif sur ce concept.
Obfuscation du code ?
Cet argument est un sophisme.
L'obscurcissement est possible dans tous les langages...
Il est aussi facile d'obscurcir du code en C ou en Java par des fonctions/méthodes qu'en C++ par 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 le Cloneable
interface en Java :
Vous êtes censé cloner l'objet implémentant cette interface. Mais vous pouvez mentir. Et créer un objet différent. En fait, cette interface est si faible que vous pourriez renvoyer un tout autre type d'objet, juste pour le plaisir :
class MySincereHandShake implements Cloneable
{
public Object clone()
{
return new MyVengefulKickInYourHead() ;
}
}
Comme le Cloneable
peut faire l'objet d'abus/obfuscation, devrait-elle être interdite au même titre que les surcharges d'opérateurs C++ ?
Nous pourrions surcharger le toString()
méthode d'un MyComplexNumber
pour qu'elle renvoie l'heure du jour sous forme de chaîne. Si la classe toString()
la surcharge doit-elle aussi être interdite ? Nous pourrions saboter MyComplexNumber.equals
pour qu'il renvoie une valeur aléatoire, modifie les opérandes... etc. etc. etc.
En Java, comme en C++, ou tout autre langage, le programmeur doit respecter un minimum de sémantique lorsqu'il écrit du code. Cela signifie qu'il faut implémenter un add
qui ajoute, et Cloneable
méthode de mise en œuvre qui clone, et une ++
que les incréments.
Qu'est-ce que l'obscurcissement de toute façon ?
Maintenant que nous savons que le code peut être saboté même à travers les méthodes vierges de Java, nous pouvons nous interroger sur la véritable utilité de la surcharge des opérateurs en C++ ?
Notation claire et naturelle : méthodes ou surcharge d'opérateurs ?
Nous allons comparer ci-dessous, pour différents cas, le "même" code en Java et en C++, pour avoir une idée du 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 n'importe quel type en C++, tant que les surcharges d'opérateurs sont fournies. En Java, lorsque A et B ne sont pas des primitives, le code peut devenir très confus, même pour les objets de type primitif (BigInteger, etc.)...
Des accesseurs et des indices naturels pour les tableaux et les conteneurs :
// 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.set("25", value) ; // method set
En Java, nous constatons que pour chaque conteneur faisant la même chose (accéder à son contenu par le biais d'un index ou d'un identifiant), nous avons une manière différente de le faire, ce qui est déroutant.
En C++, chaque conteneur utilise la même manière d'accéder à son contenu, grâce à la surcharge des opérateurs.
Manipulation naturelle de types avancés
Les exemples ci-dessous utilisent un Matrix
trouvé en utilisant les premiers liens trouvés sur Google pour " Objet Java Matrix " 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 site BigInteger
et BigDecimal
Les classes de Java souffrent de la même verbosité déroutante, alors 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
Fecteurs 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 de la surcharge d'opérateur, n'est-ce pas ? Ce n'est pas de la triche ???
-D
Un code générique ?
Le même code générique modifiant les opérandes devrait être utilisable à la fois pour les objets intégrés/primitifs (qui n'ont pas d'interface en Java), les objets standard (qui ne pourraient pas avoir la bonne interface) et les objets définis par l'utilisateur.
Par exemple, le calcul de 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.
Discussion sur la surcharge des opérateurs
Maintenant que nous avons vu des comparaisons justes entre le code C++ utilisant la surcharge d'opérateurs, et le même code en Java, nous pouvons maintenant discuter de la "surcharge d'opérateurs" en tant que concept.
La surcharge des opérateurs existait déjà avant les ordinateurs
*Même en dehors de l'informatique, il existe des surcharges d'opérateurs : Par exemple, en mathématiques, des opérateurs comme +
, -
, `` etc. sont surchargés.**
En effet, la signification de +
, -
, *
etc. change en fonction des types d'opérandes (numériques, vecteurs, fonctions d'onde quantiques, matrices, etc.)
La plupart d'entre nous, dans le cadre de nos cours de sciences, ont appris les multiples significations des opérateurs, en fonction des types d'opérandes. Les avons-nous trouvées déroutantes ?
La surcharge des opérateurs dépend de leurs opérandes.
C'est la partie la plus importante de la surcharge de l'opérateur : Comme en mathématiques, ou en physique, l'opération dépend des types de ses opérandes.
Ainsi, 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érateurs (codée en dur).
En C, le comportement réel d'un opérateur change en fonction de ses opérandes. Par exemple, l'addition de deux entiers est différente de l'addition de deux doubles, ou même d'un entier et d'un double. Il y a même tout le domaine de l'arithmétique des pointeurs (sans casting, on peut ajouter à un pointeur un entier, mais on ne peut 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 de caractères sans l'élément +
serait suffisamment ridicule pour justifier une exception dans le credo "la surcharge des opérateurs est un mal".
C'est juste que vous, en tant qu'utilisateur de C (pour des raisons historiques) ou de Java (pour des raisons de sécurité), n'avez pas l'intention d'être un expert en la matière. raisons personnelles (voir ci-dessous), vous ne pouvez pas fournir votre propre codeur.
En C++, la surcharge des opérateurs n'est pas optionnelle...
En C++, la surcharge d'opérateurs pour les types intégrés n'est pas possible (et c'est une bonne chose), mais défini par l'utilisateur peuvent avoir défini par l'utilisateur surcharges de l'opérateur.
Comme nous l'avons déjà dit, en C++, et contrairement à Java, les types 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 utilisateurs devraient pouvoir en avoir aussi.
La vérité est que, comme le toString()
, clone()
, equals()
sont pour Java ( c'est-à-dire quasi-standard ), la surcharge des opérateurs C++ fait tellement partie du C++ qu'elle devient aussi naturelle que les opérateurs C originaux ou les méthodes Java mentionnées précédemment.
Associée à la programmation par modèles, la surcharge des opérateurs devient un modèle de conception bien connu. En fait, vous ne pouvez pas aller très loin dans la STL sans utiliser des opérateurs surchargés, et sans surcharger des opérateurs 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 effectuer de soustraction dans un +
(comme dans "ne pas faire de soustraction dans une add
fonction", ou "retour de merde dans une clone
méthode").
La surcharge des cast peut être très dangereuse car elle peut conduire à des ambiguïtés. Elles doivent donc être réservées à des cas bien définis. Quant aux &&
et ||
Ne les surchargez jamais, à moins que vous ne sachiez vraiment ce que vous faites, car vous perdriez l'évaluation de court-circuit que les opérateurs natifs utilisent. &&
et ||
profiter.
Alors... Ok... Alors pourquoi ce n'est pas possible en Java ?
Parce que James Gosling l'a dit :
J'ai laissé de côté la surcharge d'opérateurs en tant que 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 celui de Stroustrup ci-dessous :
De nombreuses décisions relatives à la conception du C++ trouvent leur origine dans ma répugnance à forcer les gens à faire les choses d'une manière particulière [...] Souvent, j'ai été tenté de proscrire une fonctionnalité que je n'aimais pas personnellement, mais je me suis abstenu de le faire pour les raisons suivantes Je ne pensais pas avoir le droit d'imposer mon point de vue aux autres. .
Bjarne Stroustrup. Source : La conception et l'évolution du C++ (1.3 Contexte général)
La surcharge des opérateurs serait-elle bénéfique à 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 syntaxiques, etc.)
En C++, vous pouvez profiter de cet avantage grâce à l'humilité de Stroustrup. En Java, vous êtes tout simplement fichu à cause de l'humilité de Gosling. choix personnel .
Pourrait-on l'ajouter à Java ?
Les raisons de ne pas ajouter la surcharge des opérateurs maintenant en Java pourraient être un mélange de politique interne, d'allergie à la fonctionnalité, de méfiance des développeurs (vous savez, les saboteurs qui semblent hanter les équipes Java...), de compatibilité avec les JVM précédentes, de temps pour écrire une spécification correcte, etc.
Ne retenez donc pas votre souffle en attendant cette fonctionnalité...
Mais ils le font en C# ! !!
Ouais...
Bien que ce soit loin d'être la seule différence entre les deux langues, celle-ci ne manque jamais de m'amuser.
Apparemment, les gens de C#, avec leur "chaque primitive est une struct
et un struct
dérive de l'objet" a réussi du premier coup.