Cómo sobrecarga std::swap()

std::swap() es utilizado por muchos sexual de los recipientes (como std::list y std::vector) durante la clasificación y aún asignación.

Pero el ets de la implementación de swap() es muy generalizada y bastante ineficiente para tipos personalizados.

Por lo tanto la eficiencia puede ser adquirida por la sobrecarga de std::swap() con un tipo específico de aplicación. Pero ¿cómo se puede implementar de manera que será utilizado por la ets de contenedores?

InformationsquelleAutor Adam | 2008-08-14

4 Kommentare

  1. 126

    La forma correcta de sobrecarga de intercambio es escribir en el mismo espacio de nombres que lo que estás cambiando, por lo que se puede encontrar a través de el argumento de la búsqueda dependiente (ADL). Uno particularmente fácil cosa a 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);
            //...
        }
    };
    • Esa es la forma más limpia para proporcionar a un usuario definido swap, pero no ayuda en el escenario el interlocutor pregunta acerca de, como std::criterio de no utilizar ADL para encontrar el espacio de intercambio.
    • En C++2003 es, en el mejor de underspecified. La mayoría de las implementaciones de hacer uso de ADL para encontrar swap, pero no, no es obligatorio, por lo que no se puede contar con él. Que puede se especializan std::swap para un determinado tipo concreto, como se muestra por la OP; sólo que no esperen que la especialización para acostumbrarse, por ejemplo, para las clases derivadas de ese tipo.
    • Me sorprendería encontrar que las implementaciones de aún no utilizar ADL para encontrar el correcto intercambio. Este es un edad el tema en el comité. Si su aplicación no utiliza ADL para encontrar swap, un reporte de bug.
    • Alguien debería comprobar la biblioteca que se incluye con MSVC. Sé que fueron una exclusión por mucho tiempo en esto.
    • No creo que sea una buena idea. Primero de todo: ¿por Qué la definición de esta función en el ámbito global sólo? No hay ninguna razón por qué no debería ser una función miembro, también. En segundo lugar, este código significa que tenemos dos implementaciones de swap ‘X’. No es la predeterminada de espacio de nombres std y este.
    • En primer lugar, estoy definición de la función en el ámbito de espacio de nombres, porque esa es la única especie de definición que el código genérico. Porque int et. al. no/no puede tener funciones miembro, std::ordenar et. al. tiene que utilice una función de establecer el protocolo. Segundo, no sé por qué usted es el objeto de tener dos implementaciones, pero la mayoría de las clases están condenados a ser ordenados de manera ineficiente si usted no puede aceptar tener un no-miembro de la swap. La sobrecarga de las reglas de asegurarse de que, si las dos declaraciones se ven, el más específico (este) será elegido cuando el intercambio se llama sin calificación.
    • Hinnant: Si su biblioteca estándar de implementación utiliza la ADL para encontrar una función llamada swap, entonces no es de fiar. Una implementación de std::sort debe cumplir con los requisitos establecidos en la norma, independientemente de si una función llamada swap existe en un espacio de nombres definido por el usuario (pero si un usuario se especializa std::swap luego de que la especialización debe cumplir con los requisitos de std::swap de lo contrario invocar un comportamiento indefinido).
    • Del comentario anterior se refiere a que C++98 y C++03 – C++11 establece los requisitos para el swap de tipos en la sección 17.6.3.2.
    • Sólo probado con gcc, su std::ordenar utiliza un método de intercambio definido de esta manera. ¿Eso significa que gcc no es compatible en este momento?
    • -1 porque un cliente de mi clase Foo que quiere usar la swap es probable que el uso de std::swap(foo1, foo2) que se silenciosamente el uso ineficiente método predeterminado.
    • Depende. Un std::sort que utiliza ADL para intercambiar elementos no conformes en C++03, pero conforme C++11. También, ¿por qué -1 una respuesta basada en el hecho de que los clientes puedan usar no-idiomáticas código?
    • También lo he usado en gcc 4.4 sin -std=c++0x, así que supongo que no cumplen. Me parece extraño, ya que gcc es muy conformes y no sabía de ningún caso que no sea la exención de exportación. También, tengo curiosidad acerca de cómo idiomáticas using std::swap; swap(foo1, foo2); es, ya que está fuera de sintonía con la inmensa mayoría del código que utiliza std::cout, std::pow etc. y así este capricho sólo sería conocida para aquellos que específicamente han investigado. Soy un aprendiz de programador de C++ y yo no lo sabía, y yo acabo de hacer un sondeo de opinión de un par de mis compañeros y ellos no lo sabía tampoco.
    • No digo que no los cumple. Desde que fue underspecified, con ADL aquí es una conformación de extensión. No muy seguro de qué decir acerca de su curiosidad; que es el lenguaje a usar a menos que usted quiere agarrar un bonito envoltorio como boost.org/doc/libs/release/libs/utility/swap.html, que puede ser llamado con calificación (boost::swap(x,y)) y el uso de ADL internamente.
    • Cómo lo que este nunca «underspecified»? La norma simplemente nunca se lo permitió! Lo que no está permitido está prohibido.
    • Si la lectura de la norma se trata de una simple cuestión de la lectura de la norma, se estaría en lo cierto :-). Por desgracia, la intención de los autores de los asuntos. Así que si la intención original era que la ADL que podría o debería ser utilizado, es underspecified. Si no, entonces es sólo un viejo y simple cambio de hora para C++0x, que es la razón por la que escribí «el mejor de los» underspecified.
    • ¿Cómo sabes que la intención de la gente? ¿Tiene usted una fuente? (la secuencia de comandos de un debate, de un hilo de discusión, una resolución, un diseño de papel puede indicar la intención)
    • Sí, él tiene una fuente! Él es Dave Abrahams

  2. 67

    Atención Mozza314

    Aquí es una simulación de los efectos de un genérico std::algorithm llamar std::swap, y hacer que el usuario proporcione su intercambio en el espacio de nombres std. Como esto es 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 el compilador imprime algo diferente, no lo está aplicando correctamente «dos-fase de búsqueda» para las plantillas.

    Si el compilador que se está conformando (a cualquiera de C++98/03/11), entonces se dará el mismo resultado que me muestran. Y en ese caso exactamente lo que usted teme que va a suceder, sucede. Y poniendo su swap en el espacio de nombres std (exp) no deje que esto suceda.

    Dave y yo somos miembros de la comisión y han estado trabajando en esta área de la standard durante una década (y no siempre de acuerdo el uno con el otro). Pero este problema ha sido resuelto por un largo tiempo, y estamos los dos de acuerdo en cómo ha sido resuelto. El desprecio de Dave opinión de los expertos/respuesta en esta zona a su propio riesgo.

    Este problema salió a la luz después de C++98 fue publicado. A partir de 2001 Dave y comencé a el trabajo de 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)

    Actualización

    Una observación que se ha hecho:

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

    funciona! Así que ¿por qué no?

    Considerar 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 funciona de nuevo. 🙁

    Por lo que podría poner swap en el espacio de nombres std y que funcione. Pero usted debe recordar para poner swap en A‘s espacio de nombres para el caso cuando se tiene una plantilla: A<T>. Y puesto que ambos casos funcionará si pones swap en A‘s espacio de nombres, es simplemente más fácil de recordar (y a enseñar a otros) que acaba de hacer es que de una manera.

    • Gracias gracias por la respuesta detallada. Estoy claramente menos informados acerca de esto y en realidad estaba preguntando cómo la sobrecarga y la especialización podría producir un comportamiento diferente. Sin embargo, no estoy sugiriendo que la sobrecarga pero especialización. Cuando me pongo template <> en su primer ejemplo puedo obtener una salida exp::swap(A, A) de gcc. Así que, ¿por qué no prefieren la especialización?
    • Excelente punto! El formato de las limitaciones de nuevo. La adición de responder…
    • Wow! Esto es realmente esclarecedor. Usted tiene definitivamente me convenció. Creo que voy a modificar ligeramente su sugerencia de utilizar el amigo de clase sintaxis de Dave Abrahams (hey puedo utilizar este operador<< demasiado! 🙂 ), a menos que tenga una razón para evitar que así (aparte de la compilación por separado). También, a la luz de esto, ¿crees que using std::swap es una excepción a la «no ponga nunca el uso de declaraciones dentro de los archivos de cabecera de la regla»? De hecho, ¿por qué no poner using std::swap dentro de <algorithm>? Supongo que podría romper una pequeña minoría de las personas del código. Tal vez van a dejar de usar de apoyo y, finalmente, poner en?
    • en la clase de amigo de la sintaxis debe ser fino. Me gustaría tratar de limitar using std::swap para el ámbito de la función dentro de los encabezados. Sí, swap es casi una palabra clave. Pero no, no es una palabra clave. Así es que mejor no para exportar a todos los espacios de nombres hasta que realmente tenga que hacerlo. swap se parece mucho a operator==. La diferencia más grande es que no siquiera se piensa en llamar a operator== calificados sintaxis del espacio de nombres (acaba de ser demasiado feo).
    • Pero si la exportación de intercambio para todos los espacios de hace algo para romper, de que algo estaba mal de la práctica, exponiendo de manera que puedan ser corregidos. Eso es una buena cosa a la derecha?
    • <encogiéndose de hombros> C++ es un ser vivo, la evolución de la lengua. Tal vez lo que proponemos será aceptable para la comunidad en 10 años. Después de todo, ese es el tiempo que se tomó para llegar a donde estamos hoy. 😉
    • «Si su compilador que se está conformando (a cualquiera de C++98/03/11), entonces se dará el mismo resultado que yo espectáculo.» ¿Cómo puede usted decir que, cuando el C++98 se pronuncia sobre el problema?
    • Su reclamo es que C++98 fue en silencio sobre el tema de las «dos de la fase de búsqueda» para las plantillas?!
    • Para los nombres completos, obviamente. Nombres incompletos están cubiertos.
    • Lo que ustedes están viendo como complicación es simplemente demasiado muchas respuestas equivocadas. No hay nada complicado sobre Dave Abrahams’ respuesta correcta: «La manera correcta de sobrecarga de intercambio es escribir en el mismo espacio de nombres que lo que estás cambiando, por lo que se puede encontrar mediante la búsqueda dependiente de argumento (ADL).»
    • Consulte stackoverflow.com/questions/21384604/… – he preguntado en StackOverflow pregunta sobre específicamente esta respuesta.
    • No es algo complicado sobre este… usted tiene que encontrar este post para (a) esta solución es la que actualmente se recomienda una (b) perder el miedo de consecuencias desconocidas (c) creer en él.
    • Lo siento. La hierba ha estado tratando de llegar este mensaje a todos desde 1998: gotw.ca/publications/mill02.htm que Él no menciona swap en este artículo. Pero esto es sólo otra aplicación de la Hierba de la Interfaz del Principio.
    • los gurús son sólo una pequeña parte de la piscina. desbordamiento de la pila, la búsqueda de google con mal formada consultas, etc, son cómo la gente hacer las cosas. He estado trabajando en la ingeniería de software en la industria por más de 10 años y mis colegas (que en su mayoría extraídas de) no sabía esto, no enseñar, y (fuerte y firmemente) le dio un codazo en una dirección diferente.
    • Me sale «exp::swap(a, a)» cuando el uso de la template<class T> struct A ejemplo.
    • Visual Studio?
    • Sí.
    • Visual Studio aún no se implementa correctamente el 2-fase de búsqueda de normas introducidas en C++98. Eso significa que en este ejemplo VS llama el mal swap. Esto añade una nueva arruga antes no había considerado: En el caso de template<class T> struct A, poniendo su swap en el espacio de nombres std hace que su código no portátiles. Pruebe su ejemplo en wandbox para ver cómo gcc y clang manejar.

  3. 53

    No está permitido (por el estándar de C++) para sobrecargar std::swap, sin embargo, usted está específicamente permitido añadir especializaciones de plantilla para sus propios tipos para el espacio de nombres std. E. g.

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

    a continuación, los usos de la ets de contenedores (y en cualquier otro lugar) va a recoger a su especialización lugar de la general.

    También tenga en cuenta que la provisión de una implementación de la clase base de swap no es lo suficientemente bueno para sus tipos derivados. E. g. 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 de ella va a usar la versión genérica de sexual, debido a que la plantilla de intercambio es una coincidencia exacta (y se evita el problema de intercambio de la ‘base’ partes de sus objetos derivados).

    NOTA: he actualizado esta para quitar el mal bits desde mi última respuesta. D’oh! (gracias puetzk y j_random_hacker para señalarla)

    • En su mayoría buenos consejos, pero tengo que -1 debido a la sutil distinción señaló puetzk especialización entre una plantilla en el espacio de nombres std (que está permitido por el estándar de C++) y sobrecarga (que no es).
    • Doh! gracias por decírmelo, ha sido un largo tiempo desde que he tocado C++ así que yo voy a culpar para que la información incorrecta no 🙂
    • Votada abajo porque de la manera correcta para personalizar swap es para hacerlo en su propio espacio de nombres (como Dave Abrahams señala en otra respuesta).
    • Mis razones para downvoting son los mismos que Howard
    • Está prohibido sobrecargar std::swap (o cualquier cosa), pero fuera de std::swap espacio de nombres?
    • Dave Abrahams: no estoy de acuerdo. En base a qué se puede alegar que su alternativa es la manera «correcta»? Como puetzk citado de la norma, esto está expresamente permitido. Aunque soy nuevo en este tema en realidad no me gusta el método que abogar porque si yo defino Foo y de intercambio de esa manera a alguien que usa mi código es probable que el uso std::swap(a, b) en lugar de swap(a, b) en Foo, que silenciosamente se utiliza la ineficiente de la versión predeterminada.
    • El espacio y el formato de las limitaciones de la área de comentarios no me dejaba completamente de respuesta para usted. Por favor vea la respuesta, he añadido el titulado «la Atención Mozza314».
    • estoy en lo cierto al pensar que esta técnica podría también fácilmente el incumplimiento de una definición de la regla? Si una unidad de traducción ha incluido <algoritmo> y una declaración de la clase Base; mientras que el otro incluye la cabecera, arriba, a continuación, tiene dos instancias diferentes de std::swap<Base>. Recuerdo que está prohibido en un conformes programa, pero el uso de esta técnica significa que usted debe prevenir con éxito a los usuarios de su clase de la redacción de una declaración forward – deben ser obligados de alguna manera a incluir siempre su cabecera a alcanzar sus metas. Este resulta ser poco práctico para alcanzar en la escala.
    • Creo que a partir de C++20 es correcta sólo para especializarse clases de plantilla, no de la plantilla de funciones.

  4. 29

    Si bien es correcto que uno no debería añadir cosas a la std:: espacio de nombres, la adición de la plantilla de especializaciones para tipos definidos por el usuario está expresamente permitido. La sobrecarga de las funciones no es. Esta es una sutil diferencia 🙂

    17.4.3.1/1
    No se ha definido para un programa de C++ para agregar declaraciones o definiciones
    para el espacio de nombres std o espacios de nombres con el espacio de nombres std, a menos que de otra manera
    especificado. Un programa puede agregar la plantilla de especializaciones para cualquier
    la biblioteca estándar de plantillas para el espacio de nombres std. Tal especialización
    (completa o parcial) de una biblioteca estándar de los resultados en undefined
    comportamiento a menos que la declaración depende de un nombre definido por el usuario de
    la vinculación externa y a menos que la especialización de plantilla cumple con los
    la biblioteca estándar de requisitos para la plantilla original.

    Una especialización de std::swap aspecto:

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

    Sin la plantilla de<> poco sería una sobrecarga, que es indefinido, en lugar de una especialización, lo cual es permitido. @Wilka del sugieren enfoque de cambiar el espacio de nombres predeterminado puede trabajar con el código de usuario (debido a la búsqueda de Koenig prefiriendo el espacio de nombres-menos de la versión), pero no se garantiza, y de hecho no es realmente supone (STL aplicación se debe usar para el completo std::swap).

    Hay un hilo en comp.lang.c++.moderado con un largo foro de discusión para profesionales del tema. La mayoría de ella es de unos parciales de especialización, a pesar de (que actualmente no hay ninguna buena manera de hacerlo).

    • Una de las razones es malo usar la función de plantilla de especialización para esto (o nada): interactúa de malas maneras con sobrecargas, de los cuales hay muchos para swap. Por ejemplo, si usted se especializan regular std::swap de tipo std::vector<mytype>&, su especialización no obtener elegido por encima de la norma del vector específico de intercambio, debido a que las especializaciones no son considerados durante la resolución de sobrecarga.
    • Esto es también lo que Meyers recomienda en Efectivo de C++ 3ed (Artículo 25, pp 106-112).
    • Si especializados (sin explícita argumentos de plantilla), orden parcial es una especialización de la de el vector versión y se usa.
    • en realidad, no, cuando usted hace orden parcial no juega ningún papel. El problema no es que no se puede llamar a todos; es lo que sucede en la presencia de al parecer-menos-específica de las sobrecargas de swap: wandbox.org/permlink/nck8BkG0WPlRtavV
    • El orden parcial es seleccione la plantilla de función para especializarse cuando el explícito especialización coincide con más de una. El ::swap sobrecarga agregó es más especializado que el std::swap sobrecarga para vector, por lo que captura la llamada y la especialización de este último es relevante. No estoy seguro de lo que es un problema práctico (pero tampoco estoy afirmando que esta es una buena idea!).
    • ah, OK, sí; yo no recuerdo que hubo un swap sobrecarga de std::vector. Es un problema práctico porque el programador piensa que él es personalizar el comportamiento de swap para un tipo específico, pero su adaptación, puede ser ineficaz por una sobrecarga específica.

Kommentieren Sie den Artikel

Bitte geben Sie Ihren Kommentar ein!
Bitte geben Sie hier Ihren Namen ein

Pruebas en línea