No pude dormir anoche y comencé a pensar en std::swap
. Aquí es el familiar versión C++ 98:Haciendo el cambio más rápido, más fácil de usar y a prueba de excepciones
template <typename T>
void swap(T& a, T& b)
{
T c(a);
a = b;
b = c;
}
Si una clase Foo
definida por el usuario utiliza ressources externos, esto es ineficiente. La expresión común es proporcionar un método void Foo::swap(Foo& other)
y una especialización de std::swap<Foo>
. Tenga en cuenta que esto no funciona con las plantillas de clase ya que no puede especializar parcialmente una plantilla de función, y la sobrecarga de nombres en el espacio de nombres std
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 los argumentos para encontrarla. Esto depende críticamente del cliente para seguir el "using std::swap
idioma" en lugar de llamar directamente al std::swap
. Muy frágil
En C++ 0x, si Foo
tiene un constructor movimiento definido por el usuario y un operador de asignación movimiento, proporcionando una swap
método personalizado y una especialización std::swap<Foo>
tiene poco o ningún beneficio de rendimiento, porque la versión C++ 0x de std::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 con más swap
ya tiene una gran cantidad de carga lejos del programador. Los compiladores actuales no generan constructores de movimiento y mueven operadores de asignación automáticamente, pero, hasta donde yo sé, esto cambiará. El único problema que queda entonces es la seguridad de excepciones, porque, en general, las operaciones de movimiento pueden arrojar, y esto abre una lata de gusanos. La pregunta "¿Cuál es exactamente el estado de un objeto movido desde?" complica las cosas más.
Entonces yo estaba pensando, ¿cuál es exactamente la semántica de std::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 mediante operaciones de movimiento no toca los recursos externos, solo las representaciones de objeto "planas".
¿Por qué no escribir simplemente una plantilla swap
que hace exactamente eso: intercambiar las representaciones del objeto?
#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 lo más eficiente posible: simplemente explota en la memoria sin procesar. No requiere ninguna intervención del usuario: no se deben definir métodos especiales de intercambio u operaciones de movimiento. Esto significa que incluso funciona en C++ 98 (que no tiene referencias rvalue, fíjate). Pero lo que es más importante, ahora podemos olvidarnos de los problemas de excepción de seguridad, porque memcpy
nunca se lanza.
puedo ver dos problemas potenciales con este enfoque:
En primer lugar, no todos los objetos están destinados a ser intercambiado. Si un diseñador de clase oculta el constructor de copia o el operador de asignación de copia, al intentar intercambiar objetos de la clase fallará en tiempo de compilación. Simplemente podemos introducir algún código muerto que comprueba si la copia y la asignación son legales del 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 trivialmente puede deshacerse del código muerto. (Probablemente haya mejores formas de verificar la "conformidad de intercambio", pero ese no es el punto. Lo que importa es que es posible).
En segundo lugar, algunos tipos pueden realizar acciones "inusuales" en el constructor de copias y en el operador de asignación de copias. Por ejemplo, pueden notificar a los observadores de su cambio. Considero que esto es un problema menor, porque tales tipos de objetos probablemente no deberían haber proporcionado operaciones de copia en primer lugar.
Háganme saber lo que piensa de este enfoque para el intercambio. ¿Funcionaría en la práctica? ¿Lo usarías? ¿Puedes identificar los tipos de bibliotecas donde se rompería esto? ¿Ves problemas adicionales? ¡Discutir!
La mayoría de los casos de uso actuales para 'std :: swap' tendrán mejores soluciones usando la semántica de movimiento de todos modos. – aschepler
Sí mover semántica y mover constructores. Vea esto, http://stackoverflow.com/questions/4820643/understanding-stdswap-what-is-the-purpose-of-tr1-remove-reference –
considere 'swap (* polymorphicPtr1, * polymorphicPtr2)' ... su La función swap intercambiaría el vtable de ambos objetos también ... lo que causará estragos si alguien llama a una función virtual o la llamada para intercambiar. – smerlin