2010-09-25 4 views
4

Note: comme noté par sellibitze Je ne suis pas à jour sur les références rvalues, donc les méthodes que je propose contiennent des erreurs, lisez son anwser pour comprendre lequel.Éliminer les temporaires dans la surcharge d'opérateur

Je lisais l'un des Linus' rant hier et il y a (quelque part) une riposte contre la surcharge de l'opérateur.

La plainte est, il semble que si vous avez un objet de type S alors:

S a = b + c + d + e; 

peut impliquer beaucoup de temporaires.

En C++ 03, nous avons copie élision pour empêcher cela:

S a = ((b + c) + d) + e; 

J'espère que le dernier ... + e est optimisé, mais je me demande combien sont créés avec temporaires définis par l'utilisateur operator+. Quelqu'un dans le sujet a suggéré l'utilisation de modèles d'expression pour résoudre le problème.

Maintenant, ce fil remonte à 2007, mais de nos jours, lorsque nous pensons élimination des temporaires, nous pensons Move. Donc, je pensais à l'ensemble des opérateurs de surcharge que nous devrions écrire pour ne pas éliminer les temporaires, mais pour limiter le coût de leur construction (voler des ressources).

S&& operator+(S&& lhs, S const& rhs) { return lhs += rhs; } 
S&& operator+(S const& lhs, S&& rhs) { return rhs += lhs; } // * 
S&& operator+(S&& lhs, S&& rhs) { return lhs += rhs; } 

Cet ensemble d'opérateurs semble-t-il suffisant? Est-ce généralisable (à votre avis)?

*: cette implémentation suppose de la commutativité, elle ne fonctionne pas pour le tristement célèbre string.

+0

Non déclaré dans la question: si cet ensemble est suffisant, cela signifie que nous avons 3 nouvelles surcharges à ajouter par opérateur ... Et en dehors de l'utilisation de Boost.Operator, je ne vois aucun moyen de les générer automatiquement. Du haut de ma tête cela signifie 8 'operator +' pour 'std :: string' à cause du mélange' std :: string' et 'char const *' ... –

+0

euhh c'est horrible. Et ils appellent cela une "solution".Pfft –

+0

@litb: Je suis d'accord avec votre sentiment ... surtout quand je pense à '-',' * ','/'et à tous les autres opérateurs similaires. Les classes numériques auront beaucoup plus de raisons d'hériter du 'boost :: addable' et je suppose. –

Répondre

4

Si vous pensez à une coutume, déplacer activée classe chaîne, la bonne façon d'exploiter toutes les combinaisons de catégories de valeur d'argument est:

S operator+(S const& lhs, S const& rhs); 
S operator+(S  && lhs, S const& rhs); 
S operator+(S const& lhs, S  && rhs); 
S operator+(S  && lhs, S  && rhs); 

Les fonctions renvoient un prvalue au lieu d'un xvalue. Renvoyer xvalues ​​ est généralement une chose très dangereuse   –   std :: move et std :: forward sont les exceptions évidentes. Si vous deviez retourner une référence rvalue vous brisaient code comme:

for (char c : my_string + other_string) { 
    //... 
} 

Cette boucle se comporte (selon 6.5.4/1 N3092) comme si le code est:

auto&& range = my_string + other_string; 

Cette à son tour, il en résulte une référence pendante. La durée de vie de l'objet temporaire n'est pas étendue car votre opérateur + ne renvoie pas de prvalue. Retourner les objets par valeur est parfaitement bien. Cela va créer des objets temporaires mais ces objets sont des valeurs, donc nous pouvons voler leurs ressources pour le rendre très efficace.

Deuxièmement, votre code devrait également pas compiler pour la même raison, ce ne compilera pas:

int&& foo(int&& x) { return x; } 

l'intérieur du corps x de la fonction est une lvalue et vous ne pouvez pas initialiser la « valeur de retour » (dans ce cas, la référence rvalue) avec une expression lvalue. Donc, vous auriez besoin d'une distribution explicite.

Troisièmement, il vous manque une surcharge const & + const &. Dans le cas où vos deux arguments sont des lvalues, le compilateur ne trouvera pas d'opérateur utilisable + dans votre cas.

Si vous ne voulez pas tant de surcharges, vous pouvez aussi écrire:

S operator+(S value, S const& x) 
{ 
    value += x; 
    return value; 
} 

Je ne volontairement pas écrit return value+=x; parce que cet opérateur retourne probablement une référence lvalue qui aurait conduit à copier la construction du valeur de retour. Avec les deux lignes que j'ai écrites, la valeur de retour sera déplacée à partir de la valeur .

S x = a + b + c + d; 

Au moins ce cas est très efficace, car il n'y a pas de copie, même si inutile impliqué le compilateur ne peut pas escamoter les copies   –   grâce à une classe de chaîne activée mouvement. En fait, avec une classe comme std :: string, vous pouvez exploiter sa fonction de membre d'échange rapide et le rendre efficace en C++ 03 et à condition que vous avez un compilateur raisonnablement intelligent (comme GCC):

S operator+(S value, S const& x) // pass-by-value to exploit copy elisions 
{ 
    S result; 
    result.swap(value); 
    result += x; 
    return result; // NRVO applicable 
} 

Voir David L'article d'Abraham Want Speed? Pass by Value. Mais ces opérateurs simples ne seront pas aussi efficaces donné:

S x = a + (b + (c + d)); 

Ici, le côté gauche de l'opérateur est toujours une lvalue. Comme l'opérateur + prend son côté gauche en valeur, cela conduit à de nombreuses copies. Les quatre surcharges d'en haut traitent parfaitement cet exemple aussi.

Ça fait longtemps que je n'ai pas lu la vieille diatribe de Linus. S'il se plaignait de copies inutiles en ce qui concerne std :: string, cette plainte n'est plus valide en C++ 0x, mais elle était à peine valide avant. Vous pouvez concaténer efficacement de nombreuses chaînes dans 03 C de:

S result = a; 
result += b; 
result += c; 
result += d; 

Mais en C++ 0x vous pouvez également utiliser l'opérateur + et std :: move. Ce sera très efficace aussi. J'ai effectivement regardé le code source de Git et sa gestion de chaîne (strbuf.h). Ça a l'air bien pensé. Excepté la fonction détachement/attachement, vous obtenez la même chose avec une chaîne std :: avec l'avantage évident que la ressource est automatiquement gérée par la classe elle-même, contrairement à l'utilisateur qui doit se rappeler d'appeler les bonnes fonctions à les bons moments (strbuf_init, strbuf_release).

+0

Merci d'avoir rattrapé l'erreur (en retournant '&&') Je n'ai pas encore eu l'occasion de jouer avec les références de rvalue, donc je ne suis pas à jour là-bas. Cela signifie néanmoins 4 surcharges pour traiter correctement ... mon ... –

Questions connexes