Comment surcharger std::swap()
-
08-06-2019 - |
Question
std::swap()
est utilisé par de nombreux conteneurs std (tels que std::list
et std::vector
) lors du tri et même de l'affectation.
Mais la mise en œuvre standard de swap()
est très généralisé et plutôt inefficace pour les types personnalisés.
Ainsi, l'efficacité peut être gagnée en surchargeant std::swap()
avec une implémentation spécifique à un type personnalisé.Mais comment pouvez-vous l'implémenter pour qu'il soit utilisé par les conteneurs std ?
La solution
La bonne façon de surcharger swap est de l'écrire dans le même espace de noms que ce que vous échangez, afin qu'il puisse être trouvé via recherche dépendante des arguments (ADL).Une chose particulièrement simple à faire est :
class X
{
// ...
friend void swap(X& a, X& b)
{
using std::swap; // bring in swap for built-in types
swap(a.base1, b.base1);
swap(a.base2, b.base2);
// ...
swap(a.member1, b.member1);
swap(a.member2, b.member2);
// ...
}
};
Autres conseils
Attention Mozza314
Voici une simulation des effets d'un générique std::algorithm
appel std::swap
, et demander à l'utilisateur de fournir son échange dans l'espace de noms std.Comme il s’agit d’une expérience, cette simulation utilise namespace exp
au lieu de namespace std
.
// simulate <algorithm>
#include <cstdio>
namespace exp
{
template <class T>
void
swap(T& x, T& y)
{
printf("generic exp::swap\n");
T tmp = x;
x = y;
y = tmp;
}
template <class T>
void algorithm(T* begin, T* end)
{
if (end-begin >= 2)
exp::swap(begin[0], begin[1]);
}
}
// simulate user code which includes <algorithm>
struct A
{
};
namespace exp
{
void swap(A&, A&)
{
printf("exp::swap(A, A)\n");
}
}
// exercise simulation
int main()
{
A a[2];
exp::algorithm(a, a+2);
}
Pour moi, cela s'affiche :
generic exp::swap
Si votre compilateur imprime quelque chose de différent, c'est qu'il n'implémente pas correctement la « recherche en deux phases » pour les modèles.
Si votre compilateur est conforme (à l’un des C++ 98/03/11), il donnera le même résultat que celui que je montre.Et dans ce cas, exactement ce que vous craignez se produira.Et mettre votre swap
dans l'espace de noms std
(exp
) ne l’a pas empêché.
Dave et moi sommes tous deux membres du comité et travaillons sur ce domaine de la norme depuis une décennie (et pas toujours d'accord les uns avec les autres).Mais cette question est réglée depuis longtemps et nous sommes tous deux d’accord sur la manière dont elle a été réglée.Ignorez l'opinion/la réponse d'expert de Dave dans ce domaine à vos risques et périls.
Ce problème est apparu après la publication de C++98.À partir de 2001 environ, Dave et moi avons commencé à travailler dans ce domaine.Et voici la solution moderne :
// simulate <algorithm>
#include <cstdio>
namespace exp
{
template <class T>
void
swap(T& x, T& y)
{
printf("generic exp::swap\n");
T tmp = x;
x = y;
y = tmp;
}
template <class T>
void algorithm(T* begin, T* end)
{
if (end-begin >= 2)
swap(begin[0], begin[1]);
}
}
// simulate user code which includes <algorithm>
struct A
{
};
void swap(A&, A&)
{
printf("swap(A, A)\n");
}
// exercise simulation
int main()
{
A a[2];
exp::algorithm(a, a+2);
}
La sortie est :
swap(A, A)
Mise à jour
Un constat a été fait :
namespace exp
{
template <>
void swap(A&, A&)
{
printf("exp::swap(A, A)\n");
}
}
travaux!Alors pourquoi ne pas utiliser ça ?
Considérons le cas où votre A
est un modèle de classe :
// simulate user code which includes <algorithm>
template <class T>
struct A
{
};
namespace exp
{
template <class T>
void swap(A<T>&, A<T>&)
{
printf("exp::swap(A, A)\n");
}
}
// exercise simulation
int main()
{
A<int> a[2];
exp::algorithm(a, a+2);
}
Maintenant, ça ne marche plus.:-(
Donc tu pourrais mettre swap
dans l'espace de noms std et faites-le fonctionner.Mais vous devrez vous rappeler de mettre swap
dans A
L'espace de noms de pour le cas où vous avez un modèle : A<T>
.Et puisque les deux cas fonctionneront si vous mettez swap
dans A
Dans l'espace de noms de, il est tout simplement plus facile de se souvenir (et d'enseigner aux autres) de le faire d'une seule manière.
Vous n'êtes pas autorisé (par la norme C++) à surcharger std::swap, mais vous êtes spécifiquement autorisé à ajouter des spécialisations de modèles pour vos propres types à l'espace de noms std.Par exemple.
namespace std
{
template<>
void swap(my_type& lhs, my_type& rhs)
{
// ... blah
}
}
alors les utilisations dans les conteneurs std (et partout ailleurs) choisiront votre spécialisation au lieu de la spécialisation générale.
Notez également que fournir une implémentation de classe de base de swap n'est pas suffisant pour vos types dérivés.Par exemple.si tu as
class Base
{
// ... stuff ...
}
class Derived : public Base
{
// ... stuff ...
}
namespace std
{
template<>
void swap(Base& lha, Base& rhs)
{
// ...
}
}
cela fonctionnera pour les classes de base, mais si vous essayez d'échanger deux objets dérivés, il utilisera la version générique de std car l'échange basé sur un modèle correspond exactement (et évite le problème de l'échange uniquement des parties « de base » de vos objets dérivés). ).
NOTE:J'ai mis à jour ceci pour supprimer les éléments erronés de ma dernière réponse.Oh!(merci puetzk et j_random_hacker de l'avoir signalé)
Bien qu'il soit exact qu'il ne faut généralement pas ajouter d'éléments au std ::espace de noms, l'ajout de spécialisations de modèles pour les types définis par l'utilisateur est spécifiquement autorisé.La surcharge des fonctions ne l'est pas.C'est une différence subtile :-)
17.4.3.1/1 Il n'est pas défini pour un programme C ++ pour ajouter des déclarations ou des définitions à l'espace de noms STD ou des espaces de noms avec Namespace Std, sauf indication contraire.Un programme peut ajouter des spécialisations de modèle pour tout modèle de bibliothèque standard à Namespace Std.Une telle spécialisation (complète ou partielle) d'une bibliothèque standard entraîne un comportement non défini, sauf si la déclaration dépend d'un nom défini par l'utilisateur de liaison externe et à moins que la spécialisation du modèle réponde aux exigences de la bibliothèque standard pour le modèle d'origine.
Une spécialisation de std::swap ressemblerait à :
namespace std
{
template<>
void swap(myspace::mytype& a, myspace::mytype& b) { ... }
}
Sans le bit template<>, il s'agirait d'une surcharge, qui n'est pas définie, plutôt que d'une spécialisation, qui est autorisée.L'approche suggérée par @Wilka consistant à modifier l'espace de noms par défaut peut fonctionner avec le code utilisateur (en raison de la recherche de Koenig préférant la version sans espace de noms), mais ce n'est pas garanti, et en fait n'est pas vraiment censé le faire (l'implémentation STL devrait utiliser pleinement le -qualifié std::swap).
Il y a un fil de discussion sur comp.lang.c++.modéré avec un long discussion du sujet.Il s'agit cependant en grande partie d'une spécialisation partielle (ce qu'il n'existe actuellement aucun bon moyen de faire).