
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?

david abrahams
La forma correcta de sobrecargar std::swap
La 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);
// ...
}
};

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

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)

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).
los Intercambiable página movida a es.cppreference.com/w/cpp/named_req/Swappable
–Andrew Keeton
29 de noviembre de 2018 a las 19:57