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 ?

Était-ce utile?

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 AL'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 ADans 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).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top