33 votes

Surcharge C++ equal(==), raccourci ou meilleur moyen de comparer tous les attributs

Je dois surcharger un opérateur == en C++ pour une classe avec de nombreux attributs.
L'opérateur doit retourner vrai, si et seulement si tous les attributs sont égaux. Un raccourci pourrait être utile, si ces attributs changent dans le temps, pour éviter les bogues.

Existe-t-il un raccourci pour comparer tous les attributs d'une classe ?

0 votes

Vous pouvez utiliser memcmp si votre objet est POD ou une grande partie de celui-ci POD(sur cette partie)

0 votes

Vous pouvez écrire un script (dans votre éditeur s'il le supporte. Les substitutions d'expressions régulières de Vim pourraient le faire, par exemple) pour prendre une copie des lignes de déclaration et les transformer en element == other.element &&

8 votes

@user3545806 memcmp ne tiendra pas compte du rembourrage, donc cela ne fonctionnera pas.

44voto

Barry Points 45207

Il n'y a pas de raccourci. Vous devrez tout répertorier.

Certaines sources d'erreur peuvent être réduites en introduisant une fonction membre nommée tied() comme :

struct Foo {
    A a;
    B b;
    C c;
    ...

private:
    auto tied() const { return std::tie(a, b, c, ...); }
};

Pour que votre operator== peut juste utiliser ça :

bool operator==(Foo const& rhs) const { return tied() == rhs.tied(); }

Cela vous permet de ne lister qu'une seule fois tous vos membres. Mais c'est à peu près tout. Vous devez encore les lister (vous pouvez donc toujours en oublier un).


Il existe une proposition ( P0221R0 ) pour créer une operator== mais je ne sais pas si elle sera acceptée.


La proposition ci-dessus a été rejetée en faveur d'une orientation différente concernant les comparaisons. C++20 vous permettra d'écrire :

struct Foo {
    A a;
    B b;
    C c;

    // this just does memberwise == on each of the members
    // in declaration order (including base classes)
    bool operator==(Foo const&) const = default;
};

17voto

NathanOliver Points 10062

À partir de C++11, avec l'introduction de tuples nous avons aussi std::tie() . Cela permet de créer un tuple à partir d'un ensemble de variables et d'appeler une fonction de comparaison pour toutes ces variables. Vous pouvez l'utiliser comme

struct Foo
{
    int a,b,c,d,e,f;
    bool operator==(const Foo& rhs) { return std::tie(a,b,c,d,e,f) == std::tie(rhs.a,rhs.b,rhs.c,rhs.d,rhs.e,rhs.f); }
};

Vous devez toujours énumérer tous les membres que vous voulez vérifier, mais cela rend les choses plus faciles. Vous pouvez également l'utiliser pour faciliter les comparaisons de type "moins que" et "plus que".

Il faut également noter que les variables sont vérifiées dans l'ordre dans lequel vous les fournissez à tie . Ceci est important pour les comparaisons inférieures et supérieures à.

std::tie(a,b) < std::tie(rhs.a, rhs.b);

Il ne s'agit pas nécessairement de la même chose que

std::tie(b,a) < std::tie(rhs.b, rhs.a);

5voto

Philipp Claßen Points 4863

Pour le moment, il n'y a pas de raccourci mais il est prévu d'ajouter un support pour celui-ci ( P0221R0 ).

Bjarne Stroustrup a récemment écrit un billet de blog à ce sujet : Un peu de contexte pour la proposition de comparaison par défaut

En C++14, il n'y a rien de mieux que de lister tous les membres et de les comparer, ce qui est source d'erreurs. Pour citer Bjarne :

L'argument décisif en faveur des comparaisons par défaut n'est pas vraiment la commodité, mais le fait que les gens se trompent dans les opérateurs d'égalité.

2voto

muXXmit2X Points 874

La seule façon de le faire est malheureusement de vérifier tous les attributs. L'avantage est que si vous combinez toutes vos vérifications à l'aide de la fonction && il cessera de s'évaluer après la première fausse déclaration. (évaluation en court-circuit)

Ainsi, par exemple false && (4 == 4) . Le programme n'évaluerait jamais le 4 == 4 partie puisque toutes les déclarations combinées par && doivent être true pour obtenir true comme résultat final. Est-ce que cela a un sens ?

0 votes

C'est ce qu'on appelle l'évaluation des courts-circuits. Il n'y a pas vraiment d'autre solution.

0 votes

De plus, concaténation n'est pas le bon mot ici.

0 votes

@erip Je suis désolé. Je ne suis pas un locuteur natif. Quel serait un meilleur mot dans cette situation ?

1voto

kwarnke Points 31

Il y a un pas vers le operator== possible. Vous pouvez générer le code connexe à partir d'une table de définition à l'aide de ce qu'on appelle le X-Macro . Le tableau pourrait ressembler à

#define MEMBER_TBL                    \
/*type        ,name ,default*/        \
X(int         ,_(i) ,42     )         \
X(float       ,_(f) ,3.14   )         \
X(std::string ,  t  ,"Hello")         \

El _() est nécessaire pour éviter un , sur la génération le std::tie() appel. Assurez-vous que le dernier élément est w.o. _() . L'utilisation pour générer les membres est :

struct Foo
{
#define _(x) x
#define X(type, name, default) type name{default};
    MEMBER_TBL
#undef X
#undef _
}

Cela génère :

struct Foo
{
    int i{42}; float f{3.14}; std::string t{"Hello"};
}

Pour générer le operator== que vous pouvez utiliser :

bool operator==(Foo const& other) const {
        return  std::tie(
#define _(x) x,
#define X(type, name, default) this->name
            MEMBER_TBL
#undef X
        ) == std::tie(
#define X(type, name, default) other.name
            MEMBER_TBL
#undef X
#undef _
        );
    }

ce qui donne lieu à

bool operator==(Foo const& other) const {
    return std::tie(
                     this->i, this->f, this->t
    ) == std::tie(
                  other.i, other.f, other.t
    );
}

Pour ajouter de nouveaux membres, il suffit d'ajouter une nouvelle entrée à la première table. Tout le reste est généré automatiquement.

Un autre avantage est que vous pouvez ajouter simplement un dump() méthode comme

void print(void) const { 
    #define STR(x) #x
    #define _(x) x
    #define X(type, name, default)            \
            std::cout <<                      \
                STR(name) << ": " << name << " ";
            MEMBER_TBL
    #undef X
    #undef _
    #undef STR
            std::cout << std::endl;
        }

ce qui donne lieu à

void print() const {
    std::cout << "i" << ": " << i << " "; std::cout << "f" << ": " << f << " "; std::cout << "t" << ": " << t << " ";
    std::cout << std::endl;
}

Toutes les informations concernant les membres peuvent être ajoutées à la table en un seul endroit (point unique d'information) et extraites ailleurs si nécessaire.

Un travail Démo .

0 votes

Il convient de mentionner que cette technique doit être réservée à des cas très exceptionnels. Par exemple, je l'ai utilisée dans un exemple où j'avais une classe de paramètres de configuration. Chaque fois que vous deviez introduire un nouveau paramètre, vous deviez mettre à jour plusieurs emplacements dans le code, ce qui était source d'erreurs. À mon avis, l'utilisation de l'approche Macro X a simplifié la maintenance, mais elle reste un peu controversée, donc je ne la recommanderais qu'en dernier recours. Mais savoir que cette technique existe est en effet utile.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X