Il existe plusieurs façons d'écrire swap
certains meilleurs que d'autres. Avec le temps, cependant, on a constaté qu'une seule définition était la plus efficace. Réfléchissons à la manière dont nous pourrions envisager l'écriture d'une swap
función.
Nous voyons d'abord que les conteneurs comme std::vector<>
ont une fonction membre à argument unique swap
comme :
struct vector
{
void swap(vector&) { /* swap members */ }
};
Naturellement, notre classe devrait en faire autant, non ? Eh bien, pas vraiment. La bibliothèque standard a toutes sortes de choses inutiles et un membre swap
est l'un d'entre eux. Pourquoi ? Continuons.
Ce que nous devrions faire est d'identifier ce qui est canonique, et ce que notre classe besoins à faire pour travailler avec lui. Et la méthode canonique d'échange est avec std::swap
. C'est la raison pour laquelle les fonctions membres ne sont pas utiles : elles ne correspondent pas à la manière dont nous devrions échanger les choses, en général, et n'ont aucune incidence sur le comportement de std::swap
.
Bien alors, pour faire std::swap
travail que nous devons fournir (et std::vector<>
aurait dû fournir) une spécialisation de std::swap
n'est-ce pas ?
namespace std
{
template <> // important! specialization in std is OK, overloading is UB
void swap(myclass&, myclass&)
{
// swap
}
}
Cela fonctionnerait certainement dans ce cas, mais il y a un problème flagrant : les spécialisations de fonctions ne peuvent pas être partielles. En d'autres termes, 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 toujours. Il doit y avoir un meilleur moyen.
C'est le cas ! Nous pouvons utiliser un friend
et le trouver grâce à la fonction ADL :
namespace xyz
{
struct myclass
{
friend void swap(myclass&, myclass&);
};
}
Quand nous voulons échanger quelque chose, nous associons † std::swap
et ensuite faire 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'un friend
fonction ? Il existe une certaine confusion dans ce domaine.
Avant que le C++ ne soit standardisé, friend
les fonctions ont fait quelque chose appelé "injection de nom d'ami", où le code se comporte comme si si la fonction avait été écrite dans l'espace de nom environnant. Par exemple, il s'agissait d'équivalents pré-standard :
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é. Le site friend
pourrait alors seulement être trouvée via ADL ; si vous la vouliez comme une fonction libre, elle devait être déclarée comme telle ( voir ceci par exemple). Mais voilà, il y avait un problème.
Si vous utilisez seulement std::swap(x, y)
votre surcharge sera jamais être trouvé, parce que vous avez explicitement dit "regarder dans std
et nulle part ailleurs" ! C'est pourquoi certaines personnes ont suggéré d'écrire deux fonctions : l'une comme une fonction à trouver via ADL et l'autre pour traiter les données explicites std::
qualifications.
Mais comme nous l'avons vu, cela ne peut pas fonctionner dans tous les cas, et nous nous retrouvons avec un vilain désordre. Au lieu de cela, l'échange idiomatique a suivi l'autre voie : au lieu de faire en sorte que les classes fournissent std::swap
c'est le travail des échangeurs de s'assurer qu'ils n'utilisent pas de produits qualifiés. swap
comme ci-dessus. Et cela a tendance à fonctionner assez bien, 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 se contente de faire un appel non qualifié à swap
con std::swap
comme espace de nom associé. Cela permet de rendre les choses plus succinctes, mais c'est quand même dommage.
Notez qu'il n'y a pas de changement dans C++11 au comportement de std::swap
ce que j'ai cru à tort, comme d'autres, être le cas. Si vous avez été mordu par ça, lire ici .
En résumé, la fonction membre n'est que du bruit, la spécialisation est laide et incomplète, mais la friend
La fonction est complète et fonctionne. Et lorsque vous échangez, utilisez soit boost::swap
ou un swap
con std::swap
associé.
†Informellement, un nom est associé à s'il sera pris en compte lors d'un appel de fonction. Pour les détails, lisez le §3.4.2. Dans ce cas, std::swap
n'est normalement pas prise en compte, mais nous pouvons associé (l'ajouter à l'ensemble des surcharges considérées par les personnes non qualifiées). swap
), ce qui permet de le trouver.
0 votes
Si l'on considère la question secondaire sur le for basé sur l'intervalle, il est préférable d'écrire des fonctions membres et de laisser l'accès à l'intervalle sur begin() et end() dans l'espace de noms std (§24.6.5), le for basé sur l'intervalle les utilise en interne depuis l'espace de noms global ou std (voir §6.5.4). Cependant, il vient avec un inconvénient que ces fonctions font partie de l'en-tête <iterator>, si vous ne l'incluez pas, vous pourriez vouloir les écrire vous-même.
8 votes
Pourquoi n'est-il pas statique - parce qu'un
friend
n'est pas du tout une fonction membre.