Pergunta

std::swap() é usado por muitos contêineres padrão (como std::list e std::vector) durante a classificação e até mesmo a atribuição.

Mas a implementação padrão de swap() é muito generalizado e bastante ineficiente para tipos personalizados.

Assim, a eficiência pode ser obtida sobrecarregando std::swap() com uma implementação específica de tipo personalizado.Mas como você pode implementá-lo para que seja usado pelos contêineres padrão?

Foi útil?

Solução

A maneira correta de sobrecarregar o swap é escrevê-lo no mesmo namespace do que você está trocando, para que possa ser encontrado via pesquisa dependente de argumento (ADL).Uma coisa particularmente fácil de fazer é:

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);
        // ...
    }
};

Outras dicas

Atenção Mozza314

Aqui está uma simulação dos efeitos de um genérico std::algorithm ligando std::swap, e fazer com que o usuário forneça sua troca no namespace std.Como se trata de um experimento, esta simulação usa namespace exp em vez 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);
}

Para mim isso imprime:

generic exp::swap

Se o seu compilador imprimir algo diferente, ele não estará implementando corretamente a "pesquisa em duas fases" para modelos.

Se o seu compilador estiver em conformidade (com qualquer C++ 98/03/11), ele fornecerá a mesma saída que mostro.E nesse caso, exatamente o que você teme que aconteça, acontece.E colocando o seu swap no espaço para nome std (exp) não impediu que isso acontecesse.

Dave e eu somos membros do comitê e trabalhamos nesta área do padrão há uma década (e nem sempre em acordo um com o outro).Mas esta questão já foi resolvida há muito tempo e ambos concordamos na forma como foi resolvida.Desconsidere a opinião/resposta do especialista de Dave nesta área por sua própria conta e risco.

Esse problema veio à tona após a publicação do C++ 98.A partir de 2001, Dave e eu começamos a trabalhar esta área.E esta é a solução moderna:

// 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);
}

A saída é:

swap(A, A)

Atualizar

Foi feita uma observação que:

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

}

funciona!Então por que não usar isso?

Considere o caso em que seu A é um modelo 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);
}

Agora não funciona novamente.:-(

Então você poderia colocar swap no namespace std e faça funcionar.Mas você precisará se lembrar de colocar swap em Anamespace para o caso quando você tem um modelo: A<T>.E como ambos os casos funcionarão se você colocar swap em Ano namespace, é mais fácil lembrar (e ensinar os outros) de fazer isso dessa maneira.

Você não tem permissão (pelo padrão C++) para sobrecarregar std::swap, no entanto, você tem permissão específica para adicionar especializações de modelo para seus próprios tipos ao namespace std.Por exemplo.

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

então os usos nos contêineres std (e em qualquer outro lugar) escolherão sua especialização em vez da geral.

Observe também que fornecer uma implementação de classe base de swap não é bom o suficiente para seus tipos derivados.Por exemplo.se você tem

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

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

isso funcionará para classes Base, mas se você tentar trocar dois objetos Derivados ele usará a versão genérica do std porque a troca modelada é uma correspondência exata (e evita o problema de trocar apenas as partes 'base' de seus objetos derivados ).

OBSERVAÇÃO:Atualizei isso para remover os bits errados da minha última resposta.Ah!(obrigado puetzk e j_random_hacker por apontar isso)

Embora seja correto que geralmente não se deve adicionar coisas ao std::namespace, é especificamente permitido adicionar especializações de modelo para tipos definidos pelo usuário.Sobrecarregar as funções não é.Esta é uma diferença sutil :-)

17.4.3.1/1 É indefinido para um programa C ++ para adicionar declarações ou definições ao espaço para nome STD ou namespaces com namespace std, a menos que especificado de outra forma.Um programa pode adicionar especializações de modelo para qualquer modelo de biblioteca padrão ao espaço para nome STD.Essa especialização (completa ou parcial) de uma biblioteca padrão resulta em comportamento indefinido, a menos que a declaração dependa de um nome definido pelo usuário da ligação externa e, a menos que a especialização do modelo atenda aos requisitos de biblioteca padrão para o modelo original.

Uma especialização de std::swap seria semelhante a:

namespace std
{
    template<>
    void swap(myspace::mytype& a, myspace::mytype& b) { ... }
}

Sem o bit template<> seria uma sobrecarga, que é indefinida, em vez de uma especialização, que é permitida.A abordagem sugerida por @Wilka para alterar o namespace padrão pode funcionar com o código do usuário (devido à pesquisa de Koenig preferir a versão sem namespace), mas não é garantido que isso aconteça e, na verdade, não é suposto que funcione (a implementação STL deve usar o totalmente -std qualificado::swap).

Existe um tópico em comp.lang.c++.moderated com um longo discussão do tema.A maior parte é sobre especialização parcial (o que atualmente não há uma boa maneira de fazer).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top