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'une swap
fonction.
Nous voyons d'abord que les conteneurs comme std::vector<>
ont une fonction membre à argument unique swap
, telle que:
struct vector
{
void swap(vector&) { /* swap members */ }
};
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 de std::swap
.
Eh bien, pour faire du std::swap
travail, nous devrions fournir (et std::vector<>
aurions dû fournir) une spécialisation 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 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:
namespace std
{
template <typename T>
void swap<T>(myclass<T>&, myclass<T>&) // 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 friend
fonction et la trouver via ADL :
namespace xyz
{
struct myclass
{
friend void swap(myclass&, myclass&);
};
}
Quand nous voulons échanger quelque chose, nous associons † std::swap
puis faisons un appel non qualifié:
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 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:
struct foo
{
friend void bar()
{
// baz
}
};
// turned into, pre-standard:
struct foo
{
friend void bar();
};
void bar()
{
// baz
}
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 dedans std
, 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 des std::
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
, avec std::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, utilisez boost::swap
ou non qualifié swap
avec std::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és swap
), permettant de la retrouver.