fonction membre d’échange public ami

Dans la belle réponse à l’ idiome de copie et d’échange, il y a un morceau de code dont j’ai besoin d’un peu d’aide:

class dumb_array { public: // ... friend void swap(dumb_array& first, dumb_array& second) // nothrow { using std::swap; swap(first.mSize, second.mSize); swap(first.mArray, second.mArray); } // ... }; 

et il ajoute une note

Il y a d’autres affirmations selon lesquelles nous devrions spécialiser std :: swap pour notre type, fournir un échange en classe le long d’un échange de fonction libre, etc. Mais tout cela n’est pas nécessaire: toute utilisation correcte du swap se fera par un appel non qualifié , et notre fonction sera trouvée par ADL. Une fonction fera l’affaire.

Avec un friend je suis un peu sur des termes “hostiles”, je dois l’admettre. Donc, mes principales questions sont:

  • ressemble à une fonction libre , mais à l’intérieur du corps de classe?
  • pourquoi ce swap n’est-il pas statique ? Il n’utilise évidemment aucune variable membre.
  • “Toute utilisation correcte du swap permettra-t-elle de découvrir le swap via ADL” ? ADL va rechercher les espaces de noms, non? Mais regarde-t-il aussi à l’intérieur des classes? Ou est-ce ici que l’ friend entre?

Questions annexes:

  • Avec C ++ 11, devrais-je marquer mes swap avec noexcept ?
  • Avec C ++ 11 et sa scope , devrais-je placer friend iter begin() et friend iter end() la même manière dans la classe? Je pense que l’ friend n’est pas nécessaire ici, non?

Il existe plusieurs façons d’écrire des swap , certains étant meilleurs que d’autres. Au fil du temps, cependant, on a constaté qu’une seule définition fonctionnait mieux. Considérons comment nous pourrions envisager d’écrire une fonction d’ swap .


Nous voyons d’abord que les conteneurs comme std::vector<> ont un swap fonction membre à argument unique, tel que:

 struct vector { void swap(vector&) { /* swap members */ } }; 

Naturellement, notre classe devrait aussi, non? Eh bien pas vraiment. La bibliothèque standard contient toutes sortes de choses inutiles , et un swap membres en fait partie. Pourquoi? Continuons.


Ce que nous devrions faire, c’est identifier ce qui est canonique et ce que notre classe doit faire pour travailler avec elle. Et la méthode canonique de permutation est avec std::swap . C’est la raison pour laquelle les fonctions membres ne sont pas utiles: ce n’est pas comme ça que nous devrions permuter les choses en général, et cela n’a aucune incidence sur le comportement de std::swap .

Eh bien, pour que std::swap fonctionne, nous devrions fournir (et std::vector<> devrait avoir fourni) une spécialisation de std::swap , non?

 namespace std { template <> // important! specialization in std is OK, overloading is UB void swap(myclass&, myclass&) { // swap } } 

Eh bien, cela fonctionnerait certainement dans ce cas, mais il a un problème flagrant: les spécialisations de fonction ne peuvent pas être partielles. C’est-à-dire que nous ne pouvons pas spécialiser les classes de modèles avec ceci, seulement des instanciations particulières:

 namespace std { template  void swap(myclass&, myclass&) // error! no partial specialization { // swap } } 

Cette méthode fonctionne parfois, mais pas tout le temps. Il doit y avoir un meilleur moyen.


Il y a! Nous pouvons utiliser une fonction d’ friend et la trouver via ADL:

 namespace xyz { struct myclass { friend void swap(myclass&, myclass&); }; } 

Lorsque nous voulons échanger quelque chose, nous associons std::swap et ensuite faisons un appel sans réserve:

 using std::swap; // allow use of std::swap... swap(x, y); // ...but select overloads, first // that is, if swap(x, y) finds a better match, via ADL, it // will use that instead; otherwise it falls back to std::swap 

Qu’est-ce qu’une fonction d’ friend ? Il y a confusion autour de cette zone.

Avant la normalisation de C ++, les fonctions friend faisaient quelque chose appelé “injection de nom d’amis”, où le code se comportait comme si la fonction avait été écrite dans l’espace de noms environnant. Par exemple, ceux-ci étaient équivalents pré-standard:

 struct foo { friend void bar() { // baz } }; // turned into, pre-standard: struct foo { friend void bar(); }; void bar() { // baz } 

Cependant, lorsque l’ADL a été inventé, cela a été supprimé. La fonction friend ne pourrait alors être trouvée que via ADL; si vous le vouliez comme une fonction libre, il devait être déclaré comme tel ( voir par exemple). Mais voilà! Il y avait un problème.

Si vous utilisez simplement std::swap(x, y) , votre surcharge ne sera jamais trouvée, car vous avez explicitement dit “regardez dans std , et nulle part ailleurs”! C’est pourquoi certaines personnes ont suggéré d’écrire deux fonctions: une comme fonction à trouver via ADL et l’autre pour traiter les qualifications explicites std:: .

Mais comme nous l’avons vu, cela ne peut pas fonctionner dans tous les cas, et nous nous retrouvons avec un sale bazar. Au lieu de cela, l’échange idiomatique est passé par l’autre: au lieu de faire en sorte que les classes fournissent le travail std::swap , c’est le travail des swappers de s’assurer qu’ils n’utilisent pas le swap qualifié, comme ci-dessus. Et cela a tendance à bien fonctionner, à condition que les gens le sachent. Mais c’est là que réside le problème: il est peu intuitif d’utiliser un appel sans réserve!

Pour faciliter ceci, certaines bibliothèques comme Boost ont fourni la fonction boost::swap , qui effectue simplement un appel non qualifié à swap , avec std::swap comme espace de noms associé. Cela aide à rendre les choses succinctes, mais c’est toujours une déception.

Notez qu’il n’y a pas de changement dans C ++ 11 au comportement de std::swap , ce que d’autres et moi-même pensons à tort que cela serait le cas. Si vous aviez mordu, lisez ici .


En bref: la fonction membre est simplement du bruit, la spécialisation est laide et incomplète, mais la fonction friend est complète et fonctionne. Et lorsque vous échangez, utilisez boost::swap ou un swap non qualifié associé à std::swap .


† De manière informelle, un nom est associé s’il sera pris en compte lors d’un appel de fonction. Pour les détails, lisez §3.4.2. Dans ce cas, std::swap n’est normalement pas pris en compte; mais on peut l’ associer (l’append à l’ensemble des surcharges considérées par swap non qualifié), permettant de le retrouver.

Ce code est équivalent (dans presque tous les sens) à:

 class dumb_array { public: // ... friend void swap(dumb_array& first, dumb_array& second); // ... }; inline void swap(dumb_array& first, dumb_array& second) // nothrow { using std::swap; swap(first.mSize, second.mSize); swap(first.mArray, second.mArray); } 

Une fonction amie définie dans une classe est la suivante:

  • placé dans l’espace de nommage
  • automatiquement en inline
  • capable de se référer à des membres statiques de la classe sans autre qualification

Les règles exactes sont dans la section [class.friend] (je cite les paragraphes 6 et 7 du projet C ++ 0x):

Une fonction peut être définie dans une déclaration friend d’une classe si et seulement si la classe est une classe non locale (9.8), le nom de la fonction n’est pas qualifié et la fonction a une scope d’espace de noms.

Une telle fonction est implicitement intégrée. Une fonction amie définie dans une classe est dans la scope (lexicale) de la classe dans laquelle elle est définie. Une fonction d’ami définie en dehors de la classe ne l’est pas.