Hacer el intercambio más rápido, más fácil de usar y seguro para excepciones

No pude dormir anoche y empecé a pensar enstd::swap. Aquí está la versión familiar de C ++ 98:

template <typename T>
void swap(T& a, T& b)
{
    T c(a);
    a = b;
    b = c;
}

Si una clase definida por el usuarioFoo utiliza recursos externos, esto es ineficiente. El idioma común es proporcionar un métodovoid Foo::swap(Foo& other) y una especialización destd::swap<Foo>. Tenga en cuenta que esto no funciona con la claseplantillas ya que no puede especializar parcialmente una plantilla de función y sobrecargar nombres en elstd El espacio de nombres es ilegal. La solución es escribir una función de plantilla en el propio espacio de nombres y confiar en la búsqueda dependiente de argumentos para encontrarla. Esto depende críticamente del cliente para seguir el "using std::swap modismo "en lugar de llamarstd::swap directamente. Muy frágil

En C ++ 0x, siFoo tiene un constructor de movimiento definido por el usuario y un operador de asignación de movimiento, que proporciona una función personalizadaswap método y unstd::swap<Foo> la especialización tiene poco o ningún beneficio de rendimiento, porque la versión C ++ 0x destd::swap utiliza movimientos eficientes en lugar de copias:

#include <utility>

template <typename T>
void swap(T& a, T& b)
{
    T c(std::move(a));
    a = std::move(b);
    b = std::move(c);
}

No tener que jugar conswap Ya no le quita mucha carga al programador. Los compiladores actuales todavía no generan constructores de movimiento y operadores de asignación de movimiento automáticamente, pero hasta donde yo sé, esto cambiará. El único problema que queda es la seguridad de la excepción, porque en general, las operaciones de movimiento pueden lanzar, y esto abre una lata entera de gusanos. La pregunta "¿Cuál es exactamente el estado de un objeto movido desde"? complica las cosas aún más.

Entonces estaba pensando, ¿cuáles son exactamente las semánticas destd::swap en C ++ 0x si todo va bien? ¿Cuál es el estado de los objetos antes y después del intercambio? Normalmente, el intercambio a través de operaciones de movimiento no toca recursos externos, solo las representaciones de objetos "planos".

Entonces, ¿por qué no simplemente escribir unswap plantilla que hace exactamente eso:intercambiar las representaciones de objetos?

#include <cstring>

template <typename T>
void swap(T& a, T& b)
{
    unsigned char c[sizeof(T)];

    memcpy( c, &a, sizeof(T));
    memcpy(&a, &b, sizeof(T));
    memcpy(&b,  c, sizeof(T));
}

Esto es tan eficiente como se pone: simplemente explota a través de la memoria sin procesar. No requiere ninguna intervención del usuario: no hay que definir métodos especiales de intercambio u operaciones de movimiento. Esto significa que incluso funciona en C ++ 98 (que no tiene referencias de valor, eso sí). Pero aún más importante,ahora podemos olvidarnos de los problemas de seguridad de excepción, porquememcpy nunca tira

Puedo ver dos posibles problemas con este enfoque:

Primero, no todos los objetos están destinados a ser intercambiados. Si un diseñador de clase oculta el constructor de copia o el operador de asignación de copia, tratar de intercambiar objetos de la clase debería fallar en tiempo de compilación. Simplemente podemos introducir un código muerto que verifica si la copia y la asignación son legales en el tipo:

template <typename T>
void swap(T& a, T& b)
{
    if (false)    // dead code, never executed
    {
        T c(a);   // copy-constructible?
        a = b;    // assignable?
    }

    unsigned char c[sizeof(T)];

    std::memcpy( c, &a, sizeof(T));
    std::memcpy(&a, &b, sizeof(T));
    std::memcpy(&b,  c, sizeof(T));
}

Cualquier compilador decente puede deshacerse trivialmente del código muerto. (Probablemente hay mejores formas de verificar la "conformidad de intercambio", pero ese no es el punto. Lo importante es que sea posible).

En segundo lugar, algunos tipos pueden realizar acciones "inusuales" en el constructor de copia y el operador de asignación de copia. Por ejemplo, podrían notificar a los observadores de su cambio. Considero que este es un problema menor, porque este tipo de objetos probablemente no deberían haber proporcionado operaciones de copia en primer lugar.

Por favor, hágame saber lo que piensa de este enfoque para el intercambio. ¿Funcionaría en la práctica? ¿Lo usarías? ¿Puedes identificar tipos de biblioteca donde esto se rompería? ¿Ves problemas adicionales? ¡Discutir!

Respuestas a la pregunta(5)

Su respuesta a la pregunta