Hay varias formas de escribir swap
, algunas mejores que otras. Sin embargo, con el tiempo, se descubrió que una sola definición funciona mejor. Consideremos cómo podríamos pensar en escribir una swap
función.
Primero vemos que los contenedores como std::vector<>
tienen una función miembro de argumento único swap
, como:
struct vector
{
void swap(vector&) { /* swap members */ }
};
Naturalmente, entonces, nuestra clase también debería, ¿verdad? Bueno en realidad no. La biblioteca estándar tiene todo tipo de cosas innecesarias , y un miembro swap
es una de ellas. ¿Por qué? Continúemos.
Lo que debemos hacer es identificar qué es canónico y qué necesita hacer nuestra clase para trabajar con él. Y el método canónico de intercambio es con std::swap
. Esta es la razón por la cual las funciones miembro no son útiles: no son cómo debemos intercambiar cosas, en general, y no influyen en el comportamiento de std::swap
.
Bueno, para hacer el std::swap
trabajo deberíamos proporcionar (y std::vector<>
deberíamos haber proporcionado) una especialización std::swap
, ¿verdad?
namespace std
{
template <> // important! specialization in std is OK, overloading is UB
void swap(myclass&, myclass&)
{
// swap
}
}
Bueno, eso ciertamente funcionaría en este caso, pero tiene un problema evidente: las especializaciones de funciones no pueden ser parciales. Es decir, no podemos especializar clases de plantillas con esto, solo instancias particulares:
namespace std
{
template <typename T>
void swap<T>(myclass<T>&, myclass<T>&) // error! no partial specialization
{
// swap
}
}
Este método funciona algunas veces, pero no todo el tiempo. Debe haber una mejor manera.
¡Ahi esta! Podemos usar una friend
función y encontrarla a través de ADL :
namespace xyz
{
struct myclass
{
friend void swap(myclass&, myclass&);
};
}
Cuando queremos intercambiar algo, asociamos † std::swap
y luego hacemos una llamada no calificada:
using std::swap; // allow use of std::swap...
swap(x, y); // ...but select overloads, first
// that is, if swap(x, y) finds a better match, via ADL, it
// will use that instead; otherwise it falls back to std::swap
¿Qué es una friend
función? Hay confusión en esta área.
Antes de que C ++ se estandarizara, las friend
funciones hicieron algo llamado "inyección de nombre de amigo", donde el código se comportó como si la función se hubiera escrito en el espacio de nombres circundante. Por ejemplo, estos fueron pre-estándar equivalentes:
struct foo
{
friend void bar()
{
// baz
}
};
// turned into, pre-standard:
struct foo
{
friend void bar();
};
void bar()
{
// baz
}
Sin embargo, cuando se inventó ADL, esto se eliminó. La friend
función solo se puede encontrar a través de ADL; si lo deseaba como una función libre, debía declararse así ( consulte esto , por ejemplo). Pero he aquí! Había un problema.
Si solo usa std::swap(x, y)
, su sobrecarga nunca será encontrada, porque ha dicho explícitamente "¡mire adentro std
, y en ningún otro lugar"! Es por eso que algunas personas sugirieron escribir dos funciones: una como una función que se encuentra a través de ADL , y la otra para manejar std::
calificaciones explícitas .
Pero como vimos, esto no puede funcionar en todos los casos, y terminamos con un desastre feo. En cambio, el intercambio idiomático fue por la otra ruta: en lugar de hacer que el trabajo de las clases sea el de proporcionar std::swap
, es el trabajo de los intercambiadores asegurarse de que no usen calificados swap
, como se indicó anteriormente. Y esto tiende a funcionar bastante bien, siempre y cuando la gente lo sepa. Pero ahí está el problema: ¡no es intuitivo necesitar usar una llamada no calificada!
Para facilitar esto, algunas bibliotecas como Boost proporcionaron la función boost::swap
, que solo realiza una llamada no calificada swap
, std::swap
como un espacio de nombres asociado. Esto ayuda a que las cosas vuelvan a ser concisas, pero sigue siendo un fastidio.
Tenga en cuenta que no hay ningún cambio en el comportamiento de C ++ 11 std::swap
, lo que yo y otros pensamos erróneamente que sería el caso. Si te mordió esto, lee aquí .
En resumen: la función miembro es solo ruido, la especialización es fea e incompleta, pero la friend
función está completa y funciona. Y cuando intercambie, use boost::swap
o no calificado swap
con std::swap
asociado.
† Informalmente, se asocia un nombre si se considerará durante una llamada de función. Para los detalles, lea §3.4.2. En este caso, std::swap
normalmente no se considera; pero podemos asociarlo (agregarlo al conjunto de sobrecargas consideradas por no calificado swap
), permitiendo que se encuentre.