Je suis un grand fan de laisser le compilateur faire autant de travail pour vous que possible. Lors de l'écriture d'une classe simple, le compilateur peut vous donner ce qui suit pour "libre":
operator=
)Mais cela ne semble pas vous donner d’opérateurs de comparaison - tels que operator==
ou operator!=
. Par exemple:
class foo
{
public:
std::string str_;
int n_;
};
foo f1; // Works
foo f2(f1); // Works
foo f3;
f3 = f2; // Works
if (f3 == f2) // Fails
{ }
if (f3 != f2) // Fails
{ }
Y a-t-il une bonne raison pour cela? Pourquoi effectuer une comparaison membre par membre poserait-il un problème? Évidemment, si la classe alloue de la mémoire, vous voudriez être prudent, mais pour une classe simple, le compilateur pourrait sûrement le faire pour vous?
Le compilateur ne sait pas si vous voulez une comparaison de pointeur ou une comparaison profonde (interne).
Il est plus sûr de ne pas l'implémenter et de laisser le programmeur le faire lui-même. Ensuite, ils peuvent faire toutes les hypothèses qu’ils aiment.
L'argument selon lequel si le compilateur peut fournir un constructeur de copie par défaut, il devrait pouvoir fournir un objet similaire par défaut operator==()
a une certaine signification. Je pense que la décision de Stroustrup à propos du constructeur de copie par défaut dans "La conception et l'évolution de C++" permet de deviner la raison de la décision de ne pas fournir de valeur par défaut générée par le compilateur à cet opérateur (Section 11.4.1 - Contrôle de la copie). :
Personnellement, j'estime dommage que les opérations de copie soient définies par défaut et j'interdit la copie d'objets de plusieurs de mes classes. Cependant, C++ a hérité ses constructeurs d'attribution et de copie par défaut de C et ils sont fréquemment utilisés.
Ainsi, au lieu de "pourquoi C++ n'a-t-il pas de operator==()
?" Par défaut, la question aurait dû être "pourquoi C++ a-t-il un constructeur d'assignation et de copie par défaut?", La réponse étant que ces éléments ont été inclus à contrecoeur by Stroustrup pour la compatibilité ascendante avec C (probablement la cause de la plupart des verrues de C++, mais aussi probablement la raison principale de la popularité de C++).
Pour mes propres besoins, dans mon IDE l'extrait de code que j'utilise pour les nouvelles classes contient des déclarations pour un opérateur d'affectation privé et un constructeur de copie de sorte que lorsque je génère une nouvelle classe, je ne reçois aucune affectation ni aucune copie par défaut. operations - je dois supprimer explicitement la déclaration de ces opérations du private:
section si je veux que le compilateur puisse les générer pour moi.
Même en C++ 20, le compilateur ne générera toujours pas implicitement operator==
Pour vous.
struct foo
{
std::string str;
int n;
};
assert(foo{"Anton", 1} == foo{"Anton", 1}); // ill-formed
Mais vous aurez la possibilité de explicitement par défaut ==
:
struct foo
{
std::string str;
int n;
// either member form
bool operator==(foo const&) const = default;
// ... or friend form
friend bool operator==(foo const&, foo const&) = default;
};
La valeur par défaut ==
Est définie par le membre ==
(De la même manière que le constructeur de copie par défaut effectue la construction de copie par le membre). Les nouvelles règles fournissent également la relation attendue entre ==
Et !=
. Par exemple, avec la déclaration ci-dessus, je peux écrire les deux:
assert(foo{"Anton", 1} == foo{"Anton", 1}); // ok!
assert(foo{"Anton", 1} != foo{"Anton", 2}); // ok!
Cette fonctionnalité spécifique (operator==
Par défaut et la symétrie entre ==
Et !=
) Provient de ne proposition qui faisait partie de la fonctionnalité de langage plus large qui est - operator<=>
.
À mon humble avis, il n'y a pas de "bonne" raison. La raison pour laquelle tant de gens sont d’accord avec cette décision de conception est qu’ils n’ont pas appris à maîtriser le pouvoir de la sémantique basée sur les valeurs. Les utilisateurs doivent écrire de nombreux constructeurs de copie personnalisés, opérateurs de comparaison et destructeurs, car ils utilisent des pointeurs bruts dans leur implémentation.
Lorsque vous utilisez des pointeurs intelligents appropriés (tels que std :: shared_ptr), le constructeur de copie par défaut est généralement correct et l'implémentation évidente de l'opérateur de comparaison par défaut hypothétique le serait aussi.
C++ n'a pas répondu = parce que C ne l'a pas fait, et voici pourquoi C ne fournit que default = mais non == à la première place. C voulait rester simple: C implémenté = par memcpy; cependant, == ne peut pas être implémenté par memcmp en raison du bourrage. Comme le remplissage n’est pas initialisé, memcmp dit qu’elles sont différentes bien qu’elles soient identiques. Le même problème existe pour les classes vides: memcmp dit qu'elles sont différentes car la taille des classes vides n'est pas nulle. On peut voir ci-dessus que l'implémentation de == est plus compliquée que celle de = en C. Un peu de code exemple à ce sujet. Votre correction est appréciée si je me trompe.
Dans cette vidéo Alex Stepanov, le créateur de STL répond à cette question vers 13h00. En résumé, après avoir suivi l’évolution du C++, il soutient que:
Il dit ensuite que dans le futur (lointain) == et ! = sera implicitement généré.
Il n'est pas possible de définir par défaut ==
, mais vous pouvez définir par défaut !=
via ==
que vous devriez habituellement vous définir. Pour cela, vous devriez faire les choses suivantes:
#include <utility>
using namespace std::rel_ops;
...
class FooClass
{
public:
bool operator== (const FooClass& other) const {
// ...
}
};
Vous pouvez voir http://www.cplusplus.com/reference/std/utility/rel_ops/ pour plus de détails.
De plus si vous définissez operator<
, les opérateurs pour <=,>,> = peuvent en être déduits en utilisant std::rel_ops
.
Mais vous devriez faire attention lorsque vous utilisez std::rel_ops
car des opérateurs de comparaison peuvent être déduits pour les types pour lesquels vous n’êtes pas attendu.
Une manière plus privilégiée de déduire un opérateur associé de celui de base consiste à utiliser boost :: operators .
L'approche utilisée dans boost est préférable car elle définit l'utilisation de l'opérateur pour la classe que vous souhaitez uniquement, pas pour toutes les classes de l'étendue.
Vous pouvez également générer "+" à partir de "+ =", - à partir de "- =", etc ... (voir la liste complète ici )
C++ 20 permet d'implémenter facilement un opérateur de comparaison par défaut.
Exemple de cppreference.com :
class Point {
int x;
int y;
public:
auto operator<=>(const Point&) const = default;
// ... non-comparison functions ...
};
// compiler implicitly declares operator== and all four relational operators work
Point pt1, pt2;
if (pt1 == pt2) { /*...*/ } // ok, calls implicit Point::operator==
std::set<Point> s; // ok
s.insert(pt1); // ok
if (pt1 <= pt2) { /*...*/ } // ok, makes only a single call to Point::operator<=>
C++ 0x a avait une proposition pour les fonctions par défaut, donc vous pourriez dire default operator==;
Nous avons appris qu'il était utile de rendre ces choses explicites.
Conceptuellement, il n'est pas facile de définir l'égalité. Même pour les données POD, on pourrait dire que même si les champs sont les mêmes, mais qu’il s’agit d’un objet différent (à une adresse différente), il n’est pas nécessairement égal. Cela dépend en fait de l'utilisation de l'opérateur. Malheureusement, votre compilateur n'est pas psychique et ne peut en déduire.
De plus, les fonctions par défaut sont d'excellents moyens de se tirer une balle dans le pied. Les valeurs par défaut que vous décrivez sont essentiellement là pour préserver la compatibilité avec les structures de POD. Cependant, ils causent plus d’assez de ravages chez les développeurs qu’ils oublient, ou la sémantique des implémentations par défaut.
Y a-t-il une bonne raison pour cela? Pourquoi effectuer une comparaison membre par membre poserait-il un problème?
Ce n’est peut-être pas un problème sur le plan fonctionnel, mais en termes de performances, la comparaison membre par membre par défaut est susceptible d’être moins optimale que la cession/copie membre par membre par défaut. Contrairement à l'ordre d'assignation, l'ordre de comparaison a un impact sur les performances car le premier membre inégal implique que le reste peut être ignoré. Donc, s'il y a des membres qui sont généralement égaux, vous voulez les comparer en dernier et le compilateur ne sait pas quels membres sont plus susceptibles d'être égaux.
Prenons cet exemple, où verboseDescription
est une longue chaîne sélectionnée dans un ensemble relativement petit de descriptions météorologiques possibles.
class LocalWeatherRecord {
std::string verboseDescription;
std::tm date;
bool operator==(const LocalWeatherRecord& other){
return date==other.date
&& verboseDescription==other.verboseDescription;
// The above makes a lot more sense than
// return verboseDescription==other.verboseDescription
// && date==other.date;
// because some verboseDescriptions are liable to be same/similar
}
}
(Bien entendu, le compilateur aurait le droit de ne pas tenir compte de l'ordre des comparaisons s'il reconnaissait qu'ils n'avaient aucun effet secondaire, mais il prendrait probablement son que du code source, là où il ne dispose pas de meilleures informations.)