122 votes

Comment surcharge std::swap()

std::swap() est utilisé par de nombreux std conteneurs (comme std::list et std::vector) lors du tri et de la même affectation.

Mais la mst la mise en œuvre de l' swap() est très généralisée et plutôt inefficace pour les types personnalisés.

Ainsi, l'efficacité peut être acquise par une surcharge, std::swap() avec un type personnalisé de mise en œuvre spécifiques. Mais comment pouvez-vous mettre en œuvre afin qu'il soit utilisé par le mst conteneurs?

144voto

Dave Abrahams Points 3667

La bonne façon de surcharge de swap est de l'écrire dans le même espace de noms que ce que vous êtes à l'échange de fichiers, de sorte qu'il peut être trouvé via argument dépendant de recherche (ADL). Il est particulièrement facile chose à 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);
        // ...
    }
};

77voto

Howard Hinnant Points 59526

Attention Mozza314

Voici une simulation des effets d'un générique std::algorithm appelant std::swap, et ayant à l'utilisateur de fournir à leurs swap dans l'espace de noms std. Comme c'est 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 m'affiche:

generic exp::swap

Si votre compilateur imprime quelque chose de différent, alors il n'est pas correctement mise en œuvre de "two-phase de recherche" pour les modèles.

Si votre compilateur est conforme (à l'un de C++98/03/11), puis il donnera le même résultat que je montre. Et dans ce cas, exactement ce dont vous avez peur qui va arriver, arrive. Et de mettre votre swap en l'espace de noms std (exp) n'a pas arrêter de se produire.

Dave et moi sommes tous deux membres du comité et nous avons travaillé en cette zone de la norme pour une décennie (et pas toujours en accord les uns avec les autres). Mais ce problème est réglé depuis longtemps, et nous avons tous deux d'accord sur la façon dont il a été réglé. Le mépris de Dave avis d'expert/réponse dans ce domaine à vos risques et périls.

Ce problème est venu à la lumière après le C++98 a été publié. À partir de 2001 Dave et j'ai commencé à travailler cette zone. Et c'est 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);
}

// Output is:
swap(A, A)

Mise à jour

Une observation a été faite que:

namespace exp
{

template <>
void swap(A&, A&)
{
    printf("exp::swap(A, A)\n");
}

}

œuvres! Alors pourquoi ne pas l'utiliser?

Prenons le cas que 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, il ne fonctionne pas encore. :-(

Si vous pouviez mettre des swap en l'espace de noms std et de le faire travailler. Mais vous aurez besoin de se rappeler de mettre swap en A's espace de noms pour le cas où vous avez un modèle: A<T>. Et puisque les deux cas ne fonctionnera que si vous mettez de l' swap en A's espace de noms, il est simplement plus facile à retenir (et à enseigner aux autres) de le faire que d'une seule manière.

53voto

Wilka Points 13239

Vous n'êtes pas autorisé (par la norme C++) à la surcharge de std::swap, cependant, vous êtes expressément autorisé à ajouter des spécialisations de modèle pour vos propres types de l'espace de noms std. E. g.

namespace std
{
    template<>
    void swap(my_type& lhs, my_type& rhs)
    {
       // ... blah
    }
}

alors que les usages dans la prévention des mst conteneurs (et partout ailleurs) choisissez votre spécialisation, au lieu de la générale.

Notez également que la fourniture d'une classe de base de la mise en œuvre de swap n'est pas assez bon pour votre types dérivés. E. g. si vous avez

class Base
{
    // ... stuff ...
}
class Derived : public Base
{
    // ... stuff ...
}

namespace std
{
    template<>
    void swap(Base& lha, Base& rhs)
    {
       // ...
    }
}

cela fonctionne pour les classes de Base, mais si vous essayez de permuter deux objets Dérivés, il va utiliser la version générique de mst parce que le basé sur un modèle de swap est une correspondance exacte (et ça évite le problème de la seule permutation de la "base" des parties de vos objets dérivés).

NOTE: j'ai mis à jour cette option pour supprimer le mauvais bits à partir de ma dernière réponse. Oh pinaise! (merci puetzk et j_random_hacker pour le pointage)

31voto

puetzk Points 5099

Alors que c'est correct qu'on ne devrait généralement pas à ajouter des trucs pour le std:: espace de noms, en ajoutant des spécialisations de modèle pour les types définis par l'utilisateur est expressément autorisé. La surcharge de fonctions n'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 les espaces de noms avec l'espace de noms std sauf indication contraire spécifié. Un programme peut ajouter des spécialisations de modèle pour toute la bibliothèque standard du modèle de l'espace de noms std. Une telle spécialisation (complète ou partielle) de la bibliothèque standard de résultats non défini comportement, à moins que la déclaration dépend d'un nom défini par l'utilisateur de liaison externe et à moins que le modèle de la spécialisation répond à la la bibliothèque standard des exigences 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 modèle<> peu, ça serait une surcharge, ce qui n'est pas défini, plutôt qu'une spécialisation, ce qui est permis. @Wilka suggèrent une approche de l'évolution de l'espace de noms par défaut peut travailler avec le code de l'utilisateur (en raison de Koenig recherche préférant l'espace de noms moins la version), mais il n'est pas garanti, en fait, n'est pas vraiment censé (la STL mise en œuvre devons utiliser le complet std::swap).

Il y a un thread sur comp.lang.c++.modéré avec une longue discussions du sujet. La plupart des il est d'environ partielle de la spécialisation, même si (il n'y a actuellement pas de bonne façon de le faire).

-1voto

David Points 7269

Le Swap est généralement efficace, car il swaps seulement les données internes de l'conteneurs. Il ne swaps de certains pointeurs internes consulter les données (éléments, processus d'allocation, de critère de tri, le cas échéant).

Vous pouvez écrire votre propre mise en œuvre de swap pour votre types définis par l'utilisateur, mais pourquoi voudriez-vous d'utiliser que la mise en œuvre sur les conteneurs standard ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X