Cómo sobrecargar std::swap()

11 minutos de lectura

Como sobrecargar stdswap
Adán

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 puede implementarlo para que sea utilizado por los contenedores estándar?

Como sobrecargar stdswap
david abrahams

La forma correcta de sobrecargar std::swapLa implementación de (también conocida como especialización) es escribirlo en el mismo espacio de nombres que lo que está intercambiando, de modo 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);
        // ...
    }
};

  • En C ++ 2003, en el mejor de los casos, está subespecificado. La mayoría de las implementaciones usan ADL para encontrar el intercambio, pero no es obligatorio, por lo que no puede contar con ello. Tú lata especializar std::swap para un tipo concreto específico como se muestra en el OP; simplemente no espere que se use esa especialización, por ejemplo, para clases derivadas de ese tipo.

    – Dave Abrahams

    1 de junio de 2010 a las 15:52

  • Me sorprendería encontrar que las implementaciones todavía no use ADL para encontrar el intercambio correcto. Esto es un viejo tema en el comité. Si su implementación no usa ADL para encontrar el intercambio, presente un informe de error.

    – Howard Hinant

    23 de febrero de 2011 a las 1:32

  • @Sascha: Primero, estoy definiendo la función en el ámbito del espacio de nombres porque ese es el único tipo de definición que importa para el código genérico. Porque int et. Alabama. no tiene/no puede tener funciones miembro, std::sort et. Alabama. tiene que usar una función libre; ellos establecen el protocolo. En segundo lugar, no sé por qué se opone a tener dos implementaciones, pero la mayoría de las clases están condenadas a clasificarse de manera ineficiente si no puede aceptar tener un intercambio de no miembros. Las reglas de sobrecarga aseguran que si se ven ambas declaraciones, se elegirá la más específica (ésta) cuando se llame a swap sin calificación.

    – Dave Abrahams

    17 de abril de 2011 a las 14:24

  • @Mozza314: Depende. A std::sort que usa ADL para intercambiar elementos no cumple con C++03 pero cumple con C++11. Además, ¿por qué -1 una respuesta basada en el hecho de que los clientes pueden usar código no idiomático?

    – JoeG

    8 de diciembre de 2011 a las 13:10


  • @curiousguy: Si leer el estándar fuera simplemente leer el estándar, tendrías razón :-). Desafortunadamente, la intención de los autores importa. Entonces, si la intención original era que ADL podría o debería usarse, está subespecificado. Si no, entonces es simplemente un cambio de última hora para C++ 0x, por lo que escribí “en el mejor de los casos” sin especificar.

    – Dave Abrahams

    24 de julio de 2013 a las 23:09

1647297974 329 Como sobrecargar stdswap
Howard Hinant

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 estándar. 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 sucediera.

Dave y yo somos miembros del comité y hemos estado trabajando en esta área de la norma durante una década (y no siempre de acuerdo entre nosotros). Pero este tema está resuelto desde hace mucho tiempo, y ambos estamos de acuerdo en cómo se ha resuelto. Ignore la opinión/respuesta experta de Dave en esta área bajo su propio riesgo.

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 hacer que funcione. Pero tendrá que recordar poner swap en AEl espacio de nombres para el caso cuando tiene una plantilla: A<T>. Y dado que ambos casos funcionarán si pones swap en Ael espacio de nombres, es más fácil de recordar (y enseñar a otros) hacerlo de esa manera.

  • Muchas gracias por la respuesta detallada. Claramente tengo menos conocimiento sobre esto y en realidad me preguntaba cómo la sobrecarga y la especialización podrían producir un comportamiento diferente. Sin embargo, no estoy sugiriendo sobrecarga sino especialización. cuando pongo template <> en tu primer ejemplo obtengo salida exp::swap(A, A) de gcc. Entonces, ¿por qué no preferir la especialización?

    – Voltrevo

    9 de diciembre de 2011 a las 0:26

  • la sintaxis de amigo en clase debería estar bien. trataría de limitar using std::swap al alcance de la función dentro de sus encabezados. Sí, swap es casi una palabra clave. Pero no, no es exactamente una palabra clave. Por lo tanto, es mejor no exportarlo a todos los espacios de nombres hasta que realmente sea necesario. swap es muy parecido operator==. La mayor diferencia es que nadie ni siquiera piensa en llamar operator== con sintaxis de espacio de nombres calificada (sería demasiado feo).

    – Howard Hinant

    9 de diciembre de 2011 a las 1:17

  • @NielKirk: lo que ve como una complicación son simplemente demasiadas respuestas incorrectas. No hay nada complicado en la respuesta correcta de Dave Abrahams: “La forma correcta de sobrecargar el intercambio es escribirlo en el mismo espacio de nombres que lo que está intercambiando, de modo que se pueda encontrar a través de una búsqueda dependiente de argumentos (ADL)”.

    – Howard Hinant

    2 oct 2013 a las 14:23

  • @codeshot: Lo siento. Herb ha estado tratando de transmitir este mensaje desde 1998: gotw.ca/publications/mill02.htm Él no menciona el intercambio en este artículo. Pero esta es solo otra aplicación del Principio de Interfaz de Herb.

    – Howard Hinant

    22 de agosto de 2015 a las 21:20

  • Visual Studio aún no implementa correctamente las reglas de búsqueda de 2 fases introducidas en C++98. Eso significa que en este ejemplo VS llama al mal swap. Esto agrega una nueva arruga que no había considerado previamente: en el caso de template<class T> struct Aponiendo tu swap en el espacio de nombres std hace que su código no sea portátil. Pruebe su ejemplo en wandbox para ver cómo gcc y clang lo manejan.

    – Howard Hinant

    5 mayo 2018 a las 15:59

1647297975 489 Como sobrecargar stdswap
wilka

No está permitido (según el estándar de 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
    }
}

luego, 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 intercambio de clase base no es lo suficientemente bueno para sus tipos derivados. por ejemplo si tienes

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 es una coincidencia exacta (y evita el problema de intercambiar solo las partes ‘base’ de sus objetos derivados ).

NOTA: Actualicé esto para eliminar las partes incorrectas de mi última respuesta. D’oh! (gracias puetzk y j_random_hacker por señalarlo)

  • Votado a la baja porque la forma correcta de personalizar el intercambio es hacerlo en su propio espacio de nombres (como señala Dave Abrahams en otra respuesta).

    – Howard Hinant

    23 de febrero de 2011 a las 1:30

  • ¿Está prohibido sobrecargar? std::swap (o cualquier otra cosa), pero fuera de std::swap espacio de nombres?

    – Cos

    28 de julio de 2011 a las 14:07

  • @HowardHinnant, Dave Abrahams: No estoy de acuerdo. ¿Sobre qué base afirma que su alternativa es la forma “correcta”? Como puetzk citó del estándar, esto está específicamente permitido. Si bien soy nuevo en este tema, realmente no me gusta el método que recomienda porque si defino Foo e intercambio de esa manera, es probable que otra persona que use mi código use std::swap(a, b) en lugar de swap( a, b) en Foo, que utiliza silenciosamente la versión predeterminada ineficiente.

    – Voltrevo

    8 de diciembre de 2011 a las 12:39

  • @Mozza314: Las limitaciones de espacio y formato del área de comentarios no me permitieron responderte completamente. Consulte la respuesta que he agregado titulada “Atención Mozza314”.

    – Howard Hinant

    8 de diciembre de 2011 a las 23:53

  • @HowardHinnant, ¿tengo razón al pensar que esta técnica también podría violar fácilmente la regla de una definición? Si una unidad de traducción ha incluido y una declaración directa de clase Base; mientras que otro incluye el encabezado, arriba, entonces tiene dos instancias diferentes de std::swap. Recuerdo que esto está prohibido en un programa conforme, pero el uso de esta técnica significa que debe evitar con éxito que los usuarios de su clase escriban una declaración de reenvío; se les debe obligar de alguna manera a incluir siempre su encabezado para lograr sus objetivos. Esto resulta ser poco práctico para lograr a escala.

    – tiro en código

    6 de noviembre de 2016 a las 15:25


1647297975 67 Como sobrecargar stdswap
puetzk

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

17.4.3.1/1 No está definido que un programa C++ agregue declaraciones o definiciones al espacio de nombres estándar o espacios de nombres con espacio de nombres estándar 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 estándar. Tal 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 de enlace externo definido por el usuario 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 template<> sería una sobrecarga, que no está definida, en lugar de una especialización, que está permitida. El enfoque sugerido de @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 de STL debería usar el completo -calificado std::swap).

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

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad