Pregunta

std::swap() es utilizado por muchos contenedores estándar (como std::list y std::vector) durante la clasificación e incluso la asignación.

Pero la implementación estándar de swap() es muy generalizado y bastante ineficiente para tipos personalizados.

Por lo tanto, se puede ganar eficiencia sobrecargando std::swap() con una implementación específica de tipo personalizado.Pero, ¿cómo se puede implementar para que sea utilizado por los contenedores estándar?

¿Fue útil?

Solución

La forma correcta de sobrecargar el intercambio es escribirlo en el mismo espacio de nombres que lo que estás intercambiando, para que se pueda encontrar a través de búsqueda dependiente de argumentos (ADL).Una cosa particularmente fácil de hacer es:

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

Otros consejos

Atención Mozza314

Aquí hay una simulación de los efectos de un genérico. std::algorithm vocación std::swap, y hacer que el usuario proporcione su intercambio en el espacio de nombres std.Como se trata de un experimento, esta simulación utiliza namespace exp en lugar 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 mí esto imprime:

generic exp::swap

Si su compilador imprime algo diferente, entonces no está implementando correctamente la "búsqueda en dos fases" para las plantillas.

Si su compilador se ajusta (a cualquiera de C++ 98/03/11), dará el mismo resultado que muestro.Y en ese caso sucede exactamente lo que temes que suceda.Y poniendo tu swap en el espacio de nombres std (exp) no impidió que esto sucediera.

Dave y yo somos miembros del comité y hemos estado trabajando en esta área del estándar durante una década (y no siempre de acuerdo entre nosotros).Pero esta cuestión se resolvió hace mucho tiempo y ambos coincidimos en cómo se resolvió.Ignore la opinión/respuesta experta de Dave en esta área bajo su propia responsabilidad.

Este problema salió a la luz después de la publicación de C++98.A partir de 2001, Dave y yo comenzamos a trabajar esta área.Y esta es la solución 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);
}

La salida es:

swap(A, A)

Actualizar

Se ha hecho una observación de que:

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

}

¡obras!Entonces, ¿por qué no usar eso?

Considere el caso de que su A es una plantilla de clase:

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

Ahora no vuelve a funcionar.:-(

Entonces podrías poner swap en el espacio de nombres std y que funcione.Pero tendrás que recordar poner swap en AEl espacio de nombres para el caso en el que tienes una plantilla: A<T>.Y como ambos casos funcionarán si pones swap en AEn el espacio de nombres, es más fácil recordar (y enseñar a otros) hacerlo de esa manera.

No está permitido (según el estándar C++) sobrecargar std::swap, sin embargo, está específicamente permitido agregar especializaciones de plantilla para sus propios tipos al espacio de nombres estándar.P.ej.

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

entonces los usos en los contenedores estándar (y en cualquier otro lugar) elegirán su especialización en lugar de la general.

También tenga en cuenta que proporcionar una implementación de clase base de swap no es suficiente para sus tipos derivados.P.ej.si usted tiene

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

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

Esto funcionará para las clases base, pero si intenta intercambiar dos objetos derivados, usará la versión genérica de std porque el intercambio con plantilla coincide exactamente (y evita el problema de intercambiar solo las partes 'base' de sus objetos derivados). ).

NOTA:Actualicé esto para eliminar los bits incorrectos de mi última respuesta.¡Oh!(gracias puetzk y j_random_hacker por señalarlo)

Si bien es correcto que generalmente no se deben agregar cosas al std::espacio de nombres, se permite específicamente agregar especializaciones de plantilla para tipos definidos por el usuario.La sobrecarga de funciones no lo es.Esta es una diferencia sutil :-)

17.4.3.1/1 No está definido para que un programa C ++ agregue declaraciones o definiciones a STD del espacio de nombres o espacios de nombres con el espacio de nombres STD a menos que se especifique lo contrario.Un programa puede agregar especializaciones de plantilla para cualquier plantilla de biblioteca estándar al espacio de nombres STD.Dicha especialización (completa o parcial) de una biblioteca estándar da como resultado un comportamiento indefinido a menos que la declaración dependa de un nombre definido por el usuario de enlace externo y a menos que la especialización de la plantilla cumpla con los requisitos de la biblioteca estándar para la plantilla original.

Una especialización de std::swap se vería así:

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

Sin el bit plantilla<> sería una sobrecarga, que no está definida, en lugar de una especialización, que está permitida.El enfoque sugerido por @Wilka de cambiar el espacio de nombres predeterminado puede funcionar con el código de usuario (debido a que la búsqueda de Koenig prefiere la versión sin espacio de nombres), pero no está garantizado y, de hecho, no se supone que lo haga (la implementación STL debe usar el espacio de nombres completo). -std calificado::swap).

Hay un hilo en comp.lang.c++.moderated con un largo discusión del tema.Sin embargo, la mayor parte se trata de especialización parcial (que actualmente no existe una buena manera de hacerlo).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top