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


Mientras mira la documentación para std::swap, Veo muchas especializaciones.
Parece que cada contenedor STL, así como muchas otras instalaciones std 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 templada:

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 obtiene de la especialización std::pair?

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

También me pregunto si estuviera escribiendo mis propias especializaciones para clases personalizadas,
o simplemente confiar en la versión de la plantilla.

Author: Trevor Hickey, 2017-02-01

6 answers

Entonces, ¿qué se obtiene de la especialización std::pair?

Rendimiento. El intercambio genérico suele ser lo suficientemente bueno (desde C++11), pero rara vez óptimo (para std::pair, y para la mayoría de las otras 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 de forma predeterminada, pero si el perfil muestra que es un cuello de botella, sepa que hay probablemente hay que mejorar. Optimización prematura y todo eso...

 37
Author: user2079303,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-01 16:40:20

std::swap is implemented along the lines of the code below:

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

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

Entonces, ¿qué se obtiene de la especialización std::pair?

std::swap puede ser especializado de la siguiente manera (simplificada 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, swap se invoca directamente en los elementos del par usando ADL: esto permite personalizar y implementaciones potencialmente más rápidas de swap para ser utilizadas en first y second (estas implementaciones pueden explotar el conocimiento de la estructura interna de los elementos para un mayor rendimiento).

(Ver "¿Cómo habilita using std::swap ADL?" para más información.)

 32
Author: Vittorio Romeo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:00:17

Presumiblemente esto es por razones de rendimiento en el caso de que los tipos contenidos de pair son baratos para intercambiar pero caros para copiar, como vector. Dado que puede llamar a swap en first y second en lugar de hacer una copia con objetos temporales, puede proporcionar una mejora significativa en el rendimiento del programa.

 12
Author: Mark B,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-01 16:29:52

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 miembro y diferentes funciones miembro.

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

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

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

Pero eso es horrendo.

Para un tipo móvil puede agregar algunos std::move y evitar copias, pero luego todavía necesita semántica "swap" en la siguiente capa hacia abajo para tener realmente semántica de movimiento útil. En algún momento, necesitas especializarte.

 4
Author: Lightness Races in Orbit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-01 16:43:54

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

Considere algo como un tipo "Vector". El Vector tiene tres campos: tamaño, capacidad y un puntero a los datos reales. Es copy constructor y copy assignment copy los datos reales. La versión de C++11 también tiene un constructor move y una asignación move que roban el puntero, estableciendo el puntero en el objeto de origen en null.

Una implementación dedicada de intercambio vectorial puede simplemente intercambiar los campos.

Un intercambio genérico la implementación 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 dinámica de memoria.

Una implementación de swap genérica basada en el constructor move, asignación move y destructor evitará cualquier copia de datos o asignación de memoria, pero dejará algunas comprobaciones redundantes de null y null que el optimizador puede o no ser capaz de optimizar.


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

Pero ¿qué pasa si tengo un par de decir Vector y Cadena ? Quiero usar las operaciones de swap especializadas para esos tipos y, por lo tanto, necesito una operación de swap en el tipo de par que lo maneje intercambiando sus elementos componentes.

 4
Author: plugwash,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-02 04:13:02

Hay una regla (creo que proviene del Excepcional C++ de Herb Sutter o de la Efectiva serie C++ de Scott Meyer) que si su tipo puede proporcionar una implementación de swap que no arroja, o es más rápida que la función genérica std::swap, debe hacerlo como función miembro void swap(T &other).

Teóricamente, la función genérica std::swap() podría usar la magia de plantilla para detectar la presencia de un intercambio de miembros y llamarlo 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, la gente tiende a agregar sobrecargas de swap libre para llamar al (potencialmente más rápido) intercambio de miembros.

 1
Author: Marc Mutz - mmutz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-07 19:20:51