Dans la belle réponse à l' idiome copier-et-échanger, 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 prétentions que nous devrions spécialiser std :: swap pour notre type, fournir un swap en classe avec un swap de fonction libre, etc. Mais tout cela est inutile: toute utilisation correcte de swap se fera par un appel non qualifié , et notre fonction sera trouvée via ADL. Une fonction fera l'affaire.
Avec friend
je suis un peu "hostile", je dois l'admettre. Donc, mes principales questions sont:
- ressemble à une fonction libre , mais c'est à l'intérieur du corps de la classe?
- pourquoi n'est-ce pas
swap
statique ? Il n'utilise évidemment aucune variable membre. - "Toute utilisation appropriée du swap permettra de découvrir le swap via ADL" ? ADL recherchera les espaces de noms, non? Mais regarde-t-il aussi à l'intérieur des classes? Ou est-ce ici où
friend
intervient?
Questions secondaires:
- Avec C ++ 11, dois-je marquer mon
swap
s avecnoexcept
? - Avec C ++ 11 et son range-for , dois-je placer
friend iter begin()
et defriend iter end()
la même manière à l'intérieur de la classe? Je pense que cefriend
n'est pas nécessaire ici, non?
c++
c++11
friend
copy-and-swap
Towi
la source
la source
friend
fonction n'est pas du tout une fonction membre.Réponses:
Il existe plusieurs façons d'écrire
swap
, certaines meilleures que d'autres. Au fil du temps, cependant, il a été constaté qu'une seule définition fonctionnait le mieux. Voyons comment nous pourrions penser l'écriture d'uneswap
fonction.Nous voyons d'abord que les conteneurs comme
std::vector<>
ont une fonction membre à argument uniqueswap
, telle que:Naturellement, alors, notre classe devrait aussi, non? Eh bien pas vraiment. La bibliothèque standard contient toutes sortes de choses inutiles , et un membre en
swap
fait partie. Pourquoi? Continuons.Ce que nous devons faire, c'est identifier ce qui est canonique et ce que notre classe doit faire pour travailler avec. Et la méthode canonique d'échange est avec
std::swap
. C'est pourquoi les fonctions membres ne sont pas utiles: elles ne sont pas la façon dont nous devrions échanger les choses, en général, et n'ont aucune incidence sur le comportement destd::swap
.Eh bien, pour faire du
std::swap
travail, nous devrions fournir (etstd::vector<>
aurions dû fournir) une spécialisationstd::swap
, non?Eh bien, cela fonctionnerait certainement dans ce cas, mais cela pose un problème flagrant: les spécialisations de fonctions ne peuvent pas être partielles. Autrement dit, nous ne pouvons pas spécialiser les classes de modèles avec ceci, seulement des instanciations particulières:
Cette méthode fonctionne parfois, mais pas tout le temps. Il doit y avoir un meilleur moyen.
Il y a! Nous pouvons utiliser une
friend
fonction et la trouver via ADL :Quand nous voulons échanger quelque chose, nous associons †
std::swap
puis faisons un appel non qualifié:Qu'est-ce qu'une
friend
fonction? Il y a confusion dans ce domaine.Avant que C ++ ne soit normalisé, les
friend
fonctions faisaient quelque chose appelé «injection de nom d'ami», où le code se comportait comme si la fonction avait été écrite dans l'espace de noms environnant. Par exemple, il s'agissait de pré-normes équivalentes:Cependant, lorsque ADL a été inventé, cela a été supprimé. La
friend
fonction pourrait alors ne se trouve par ADL; si vous le vouliez en tant que fonction libre, il fallait le déclarer comme tel ( voir ceci , 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 "regarder dedansstd
, et nulle part ailleurs"! C'est pourquoi certains ont suggéré d'écrire deux fonctions: l'une comme fonction à trouver via ADL , et l'autre pour gérer desstd::
qualifications explicites .Mais comme nous l'avons vu, cela ne peut pas fonctionner dans tous les cas et nous nous retrouvons avec un désordre horrible. Au lieu de cela, l'échange idiomatique est allé dans l'autre voie: au lieu d'en faire le travail des classes à fournir
std::swap
, c'est le travail des échangeurs de s'assurer qu'ils n'utilisent pas qualifiéswap
, comme ci-dessus. Et cela a tendance à bien fonctionner, tant que les gens le savent. Mais c'est là que réside le problème: il n'est pas intuitif de devoir utiliser un appel non qualifié!Pour rendre cela plus facile, certaines bibliothèques comme Boost ont fourni la fonction
boost::swap
, qui ne fait qu'un appel non qualifié àswap
, avecstd::swap
comme espace de noms associé. Cela aide à rendre les choses succinctes à nouveau, mais c'est toujours une déception.Notez qu'il n'y a pas de changement dans le comportement de C ++ 11
std::swap
, ce que moi et d'autres pensions à tort que ce serait le cas. Si cela vous a mordu, lisez ici .En bref: la fonction membre n'est que du bruit, la spécialisation est moche et incomplète, mais la
friend
fonction est complète et fonctionne. Et quand vous échangez, utilisezboost::swap
ou non qualifiéswap
avecstd::swap
associé.† De manière informelle, un nom est associé s'il sera pris en compte lors d'un appel de fonction. Pour plus de détails, lisez le §3.4.2. Dans ce cas,
std::swap
n'est normalement pas pris en compte; mais on peut l' associer (l'ajouter à l'ensemble des surcharges considérées par les non qualifiésswap
), permettant de la retrouver.la source
std::vector<std::string>().swap(someVecWithData);
, ce qui n'est pas possible avec uneswap
fonction libre car les deux arguments sont passés par référence non-const.operator=
,operator+
etoperator+=
, mais clairement les opérateurs sur les classes concernées ne sont acceptées / devraient exister pour la symétrie. Il en va de même pour membreswap
+ espace de nomsswap
à mon avis.function<void(A*)> f; if(!f) { }
peut échouer simplement parce qu'ilA
déclare unoperator!
qui acceptef
aussi bien quef
le sienoperator!
(peu probable, mais cela peut arriver). Si lfunction<>
'auteur de l' auteur pensait "ohh j'ai un 'operator bool', pourquoi devrais-je implémenter 'operator!'? Cela violerait DRY!", Ce serait fatal. Vous avez juste besoin d'avoir unoperator!
implémenté pourA
, et d'A
avoir un constructeur pour afunction<...>
, et les choses vont casser, car les deux candidats nécessiteront des conversions définies par l'utilisateur.Ce code est équivalent (dans presque tous les sens) à:
Une fonction ami définie à l'intérieur d'une classe est:
inline
Les règles exactes sont dans la section
[class.friend]
(je cite les paragraphes 6 et 7 du projet C ++ 0x):la source
swap
n'est visible que par ADL. C'est un membre de l'espace de noms englobant, mais son nom n'est pas visible pour les autres formulaires de recherche de nom. EDIT: Je vois que @GMan était à nouveau plus rapide :) @Ben il en a toujours été ainsi dans l'ISO C ++ :)friend
les fonctions ne sont trouvées que par ADL, et si elles doivent simplement être des fonctions libres avecfriend
accès, elles doivent être déclarées à la fois commefriend
dans la classe et comme une déclaration de fonction libre normale en dehors de la classe. Vous pouvez voir cette nécessité dans cette réponse , par exemple.