c++ templates stl swap template-specialization

c++ - ¿Por qué hay tantas especializaciones de std:: swap?



templates stl (6)

Mientras miro la documentación para std::swap , veo muchas especializaciones.
Parece que todos los contenedores STL, así como muchas otras instalaciones estándar tienen un intercambio especializado.
Pensé que con la ayuda de plantillas, no necesitaríamos todas estas especializaciones.

Por ejemplo,
Si escribo mi propio pair , funciona correctamente con la versión con plantilla:

template<class T1,class T2> struct my_pair{ T1 t1; T2 t2; }; int main() { my_pair<int,char> x{1,''a''}; my_pair<int,char> y{2,''b''}; std::swap(x,y); }

Entonces, ¿qué se gana al especializar std::pair ?

template< class T1, class T2 > void swap( pair<T1,T2>& lhs, pair<T1,T2>& rhs );

También me pregunto si debería escribir mis propias especializaciones para clases personalizadas,
o simplemente confiando en la versión de la plantilla.


Entonces, ¿qué se gana al especializar std :: pair?

Actuación. El intercambio genérico suele ser lo suficientemente bueno (desde C ++ 11), pero rara vez es óptimo (para std::pair y para la mayoría de las demás estructuras de datos).

También me pregunto si debería escribir mis propias especializaciones para clases personalizadas o simplemente confiar en la versión de la plantilla.

Sugiero confiar en la plantilla por defecto, pero si el perfil muestra que es un cuello de botella, sepa que probablemente haya margen de mejora. Optimización prematura y todo eso ...


Es de suponer que esto es por motivos de rendimiento en el caso de que los tipos contenidos de la pair sean baratos de intercambiar pero caros de copiar, como el vector . Como puede llamar a cambio first y second lugar de hacer una copia con objetos temporales, puede proporcionar una mejora significativa al rendimiento del programa.


Existe una regla (creo que proviene de Exceptional C ++ de Herb Sutter o de la serie Effective C ++ de Scott Meyer) que si su tipo puede proporcionar una implementación de intercambio que no arroja, o es más rápida que la función genérica std::swap , debería hazlo como función miembro void swap(T &other) .

Teóricamente, la función genérica std::swap() podría usar magia de plantilla para detectar la presencia de un miembro-swap y llamar eso en lugar de hacer

T tmp = std::move(lhs); lhs = std::move(rhs); rhs = std::move(tmp);

pero nadie parece haber pensado en eso, sin embargo, por lo que las personas tienden a agregar sobrecargas de swap libre para llamar al intercambio de miembros (potencialmente más rápido).


La forma más eficiente de intercambiar dos pares no es la misma que la forma más eficiente de intercambiar dos vectores. Los dos tipos tienen una implementación diferente, diferentes variables de miembro y diferentes funciones de miembro.

No existe una forma genérica de "cambiar" dos objetos de esta manera.

Quiero decir, claro, para un tipo de copiado podrías hacer esto:

T tmp = a; a = b; b = tmp;

Pero eso es horrendo.

Para un tipo movible puedes agregar std::move y prevenir copias, pero aún necesitas la semántica "swap" en la siguiente capa para tener una semántica de movimiento útil. En algún punto, necesitas especializarte.


La razón es el rendimiento, especialmente pre c ++ 11.

Considere algo así como un tipo de "Vector". El Vector tiene tres campos: tamaño, capacidad y un puntero a los datos reales. Es el constructor de copia y la copia de la asignación de copiar los datos reales. La versión C ++ 11 también tiene un constructor de movimiento y una asignación de movimiento que roban el puntero, estableciendo el puntero en el objeto fuente como nulo.

Una implementación dedicada de swap Vector puede simplemente intercambiar los campos.

Una implementación de intercambio genérico basada en el constructor de copia, la asignación de copia y el destructor dará como resultado la copia de datos y la asignación / desasignación de memoria dinámica.

Una implementación genérica de intercambio basada en el constructor de movimientos, la asignación de movimiento y el destructor evitará cualquier copia de datos o asignación de memoria, pero dejará algunas anulaciones redundantes y nulos, que el optimizador puede o no puede optimizar.

Entonces, ¿por qué tener una implementación especializada de swap para "Pair"? Para un par de int y char no hay necesidad. Son simples tipos de datos antiguos, por lo que un intercambio genérico está bien.

Pero, ¿y si tengo un par de Vector y Cadena? Quiero utilizar las operaciones de intercambio especializadas para esos tipos y, por lo tanto, necesito una operación de intercambio en el tipo de par que lo maneja intercambiando sus elementos componentes.


std::swap se implementa siguiendo las líneas del código siguiente:

template<typename T> void swap(T& t1, T& t2) { T temp = std::move(t1); t1 = std::move(t2); t2 = std::move(temp); }

(Consulte "¿Cómo implementa la biblioteca estándar std :: swap?" Para obtener más información).

Entonces, ¿qué se gana al especializar std::pair ?

std::swap se puede especializar de la siguiente manera ( simplificado a partir de libc++ ) :

void swap(pair& p) noexcept(is_nothrow_swappable<first_type>{} && is_nothrow_swappable<second_type>{}) { using std::swap; swap(first, p.first); swap(second, p.second); }

Como puede ver, el swap se invoca directamente en los elementos del par usando ADL: esto permite que las implementaciones personalizadas y potencialmente más rápidas de swap se usen first y second (esas implementaciones pueden explotar el conocimiento de la estructura interna de los elementos para más rendimiento) .

(Consulte "¿Cómo funciona el using std::swap enable ADL?" Para obtener más información).