std::swap vs std::exchange vs intercambio operador


Una implementación de std::swap podría verse así:

template <class T> void swap (T& a, T& b)
{
  T c(std::move(a)); a=std::move(b); b=std::move(c);
}
template <class T, size_t N> void swap (T (&a)[N], T (&b)[N])
{
  for (size_t i = 0; i<N; ++i) swap (a[i],b[i]);
}

Una implementación std::exchange n3668 podría verse así:

 template< typename T, typename U = T >
   T exchange( T & obj, U && new_val )
   {
     T old_val = std::move(obj);
     obj = std::forward<U>(new_val);
     return old_val;
   }

Dice:

Para los tipos primitivos, esto es equivalente a la implementación obvia, mientras que para tipos más complejos, esta definición

  • Evita copiar el valor antiguo cuando ese tipo define un constructor de movimiento
  • Acepta cualquier tipo como nuevo valor, aprovechando cualquier asignación de conversión operador
  • Evita copiar el nuevo valor si es temporal o movido.

Elegí el nombre de simetría con atomic_exchange, ya que se comportan lo mismo excepto que esta función no es atómica.

N3746 también propone un operador de intercambio integrado que se vea así:

inline C& C::operator :=: (C&& y) &  { see below; return *this; } 
inline C& C::operator :=: (C& y)  &  { return *this :=: std::move(y); }

Por lo que deduzco, a las propuestas les gustaría que las tres opciones vivieran una al lado de la otra, en lugar de sustituirse entre sí. ¿Por qué es necesario tener ¿tres formas diferentes de intercambiar objetos?

Author: Howard Hinnant, 2013-12-28

2 answers

Std:: swap vs std:: exchange

swap(x, y) y exchange(x, y) no son lo mismo. exchange(x, y) nunca asigna un nuevo valor a y. Podrías hacerlo si lo usas así: y = exchange(x, y). Pero ese no es el caso de uso principal para exchange(x, y). N3668 incluye la declaración:

El beneficio no es enorme, pero tampoco lo es el costo de la especificación.

(con respecto a la normalización exchange).

N3668 fue votado en el borrador de trabajo de C++1y en la reunión de Bristol, abril de 2013. Las actas de la reunión indican que hubo cierta discusión sobre el mejor nombre para esta función en el Grupo de Trabajo de la Biblioteca, y que finalmente no hubo objeción a someterlo a votación formal en el comité en pleno. El voto formal estuvo fuertemente a favor de incluirlo en el borrador de trabajo, pero no fue unánime.

Bottom line: exchange es una utilidad menor, no compite con swap(x, y), y tiene muchos menos casos de uso.

Std:: swap vs operador de swap

N3553 , una revisión anterior a N3746, se discutió en el Grupo de Trabajo Evolution en la reunión de abril de 2013 en Bristol. Las actas de la reunión reconocen "molestos problemas de ADL" con std::swap(x, y), pero concluyen que un operador de intercambio no abordaría esos problemas. Debido a la compatibilidad hacia atrás, el EWG también creía que si se aceptaba, std::swap y el operador de intercambio coexistirían para siempre. El EWG decidió en Bristol no proceder con N3553.

El Sep. Las actas de la reunión del EWG de Chicago de 2013 no mencionan N3746 . Yo no estaba presente en esa reunión, pero suponer que el EWG se negó a mirar a N3746 porque de su decisión anterior en Bristol N3553.

Conclusión: El comité de C++ no parece estar avanzando con un operador de intercambio en este momento.

Actualización: ¿Puede std:: exchange ser más rápido que std:: swap?

Vista previa: No. En el mejor de los casos exchange será tan rápido como swap. En el peor de los casos, puede ser más lento.

Considere una prueba como esta:

using T = int;

void
test_swap(T& x, T& y)
{
    using std::swap;
    swap(x, y);
}

void
test_exchange(T& x, T& y)
{
    y = std::exchange(x, std::move(y));
}

¿Qué genera código más rápido?

Usando clang-O3, ambos generan código idéntico (excepto por los nombres mutilados de las funciones):

__Z9test_swapRiS_:                      ## @_Z9test_swapRiS_
    .cfi_startproc
## BB#0:                                ## %entry
    pushq   %rbp
Ltmp0:
    .cfi_def_cfa_offset 16
Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp2:
    .cfi_def_cfa_register %rbp
    movl    (%rdi), %eax
    movl    (%rsi), %ecx
    movl    %ecx, (%rdi)
    movl    %eax, (%rsi)
    popq    %rbp
    retq
    .cfi_endproc

Para algún tipo arbitrario X, que no tiene una función especializada swap, ambas pruebas generarán una llamada a X(X&&) (suponiendo que existen miembros de move para X), y dos llamadas X& operator=(X&&):

test_swap

__Z9test_swapR1XS0_:                    ## @_Z9test_swapR1XS0_
    .cfi_startproc
## BB#0:                                ## %entry
    pushq   %rbp
Ltmp0:
    .cfi_def_cfa_offset 16
Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp2:
    .cfi_def_cfa_register %rbp
    pushq   %r15
    pushq   %r14
    pushq   %rbx
    pushq   %rax
Ltmp3:
    .cfi_offset %rbx, -40
Ltmp4:
    .cfi_offset %r14, -32
Ltmp5:
    .cfi_offset %r15, -24
    movq    %rsi, %r14
    movq    %rdi, %rbx
    leaq    -32(%rbp), %r15
    movq    %r15, %rdi
    movq    %rbx, %rsi
    callq   __ZN1XC1EOS_
    movq    %rbx, %rdi
    movq    %r14, %rsi
    callq   __ZN1XaSEOS_
    movq    %r14, %rdi
    movq    %r15, %rsi
    callq   __ZN1XaSEOS_
    addq    $8, %rsp
    popq    %rbx
    popq    %r14
    popq    %r15
    popq    %rbp
    retq
    .cfi_endproc

test_exchange

    .globl  __Z13test_exchangeR1XS0_
    .align  4, 0x90
__Z13test_exchangeR1XS0_:               ## @_Z13test_exchangeR1XS0_
    .cfi_startproc
## BB#0:                                ## %entry
    pushq   %rbp
Ltmp6:
    .cfi_def_cfa_offset 16
Ltmp7:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp8:
    .cfi_def_cfa_register %rbp
    pushq   %r14
    pushq   %rbx
    subq    $16, %rsp
Ltmp9:
    .cfi_offset %rbx, -32
Ltmp10:
    .cfi_offset %r14, -24
    movq    %rsi, %r14
    movq    %rdi, %rbx
    leaq    -24(%rbp), %rdi
    movq    %rbx, %rsi
    callq   __ZN1XC1EOS_
    movq    %rbx, %rdi
    movq    %r14, %rsi
    callq   __ZN1XaSEOS_
    leaq    -32(%rbp), %rsi
    movq    %r14, %rdi
    callq   __ZN1XaSEOS_
    addq    $16, %rsp
    popq    %rbx
    popq    %r14
    popq    %rbp
    retq
    .cfi_endproc

De nuevo casi el mismo código.

Pero para tipos que tienen una swap, test_swap es probable que genere un código muy superior. Considere:

using T = std::string;

(usando libc++)

test_swap

    .globl  __Z9test_swapRNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEES6_
    .align  4, 0x90
__Z9test_swapRNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEES6_: ## @_Z9test_swapRNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEES6_
    .cfi_startproc
## BB#0:                                ## %entry
    pushq   %rbp
Ltmp0:
    .cfi_def_cfa_offset 16
Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp2:
    .cfi_def_cfa_register %rbp
    movq    16(%rdi), %rax
    movq    %rax, -8(%rbp)
    movq    (%rdi), %rax
    movq    8(%rdi), %rcx
    movq    %rcx, -16(%rbp)
    movq    %rax, -24(%rbp)
    movq    16(%rsi), %rax
    movq    %rax, 16(%rdi)
    movq    (%rsi), %rax
    movq    8(%rsi), %rcx
    movq    %rcx, 8(%rdi)
    movq    %rax, (%rdi)
    movq    -8(%rbp), %rax
    movq    %rax, 16(%rsi)
    movq    -24(%rbp), %rax
    movq    -16(%rbp), %rcx
    movq    %rcx, 8(%rsi)
    movq    %rax, (%rsi)
    popq    %rbp
    retq
    .cfi_endproc

test_exchange

    .globl  __Z13test_exchangeRNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEES6_
    .align  4, 0x90
__Z13test_exchangeRNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEES6_: ## @_Z13test_exchangeRNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEES6_
Lfunc_begin0:
    .cfi_startproc
    .cfi_personality 155, ___gxx_personality_v0
    .cfi_lsda 16, Lexception0
## BB#0:                                ## %entry
    pushq   %rbp
Ltmp9:
    .cfi_def_cfa_offset 16
Ltmp10:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp11:
    .cfi_def_cfa_register %rbp
    pushq   %r14
    pushq   %rbx
    subq    $32, %rsp
Ltmp12:
    .cfi_offset %rbx, -32
Ltmp13:
    .cfi_offset %r14, -24
    movq    %rsi, %r14
    movq    %rdi, %rbx
    movq    16(%rbx), %rax
    movq    %rax, -32(%rbp)
    movq    (%rbx), %rax
    movq    8(%rbx), %rcx
    movq    %rcx, -40(%rbp)
    movq    %rax, -48(%rbp)
    movq    $0, 16(%rbx)
    movq    $0, 8(%rbx)
    movq    $0, (%rbx)
Ltmp3:
    xorl    %esi, %esi
                                        ## kill: RDI<def> RBX<kill>
    callq   __ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE7reserveEm
Ltmp4:
## BB#1:                                ## %_ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE5clearEv.exit.i.i
    movq    16(%r14), %rax
    movq    %rax, 16(%rbx)
    movq    (%r14), %rax
    movq    8(%r14), %rcx
    movq    %rcx, 8(%rbx)
    movq    %rax, (%rbx)
    movq    $0, 16(%r14)
    movq    $0, 8(%r14)
    movq    $0, (%r14)
    movw    $0, (%r14)
Ltmp6:
    xorl    %esi, %esi
    movq    %r14, %rdi
    callq   __ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE7reserveEm
Ltmp7:
## BB#2:                                ## %_ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEaSEOS5_.exit
    movq    -32(%rbp), %rax
    movq    %rax, 16(%r14)
    movq    -48(%rbp), %rax
    movq    -40(%rbp), %rcx
    movq    %rcx, 8(%r14)
    movq    %rax, (%r14)
    xorps   %xmm0, %xmm0
    movaps  %xmm0, -48(%rbp)
    movq    $0, -32(%rbp)
    leaq    -48(%rbp), %rdi
    callq   __ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED1Ev
    addq    $32, %rsp
    popq    %rbx
    popq    %r14
    popq    %rbp
    retq
LBB1_3:                                 ## %terminate.lpad.i.i.i.i
Ltmp5:
    movq    %rax, %rdi
    callq   ___clang_call_terminate
LBB1_4:                                 ## %terminate.lpad.i.i.i
Ltmp8:
    movq    %rax, %rdi
    callq   ___clang_call_terminate
Lfunc_end0:
    .cfi_endproc
    .section    __TEXT,__gcc_except_tab
    .align  2
GCC_except_table1:
Lexception0:
    .byte   255                     ## @LPStart Encoding = omit
    .byte   155                     ## @TType Encoding = indirect pcrel sdata4
    .asciz  "\242\200\200"          ## @TType base offset
    .byte   3                       ## Call site Encoding = udata4
    .byte   26                      ## Call site table length
Lset0 = Ltmp3-Lfunc_begin0              ## >> Call Site 1 <<
    .long   Lset0
Lset1 = Ltmp4-Ltmp3                     ##   Call between Ltmp3 and Ltmp4
    .long   Lset1
Lset2 = Ltmp5-Lfunc_begin0              ##     jumps to Ltmp5
    .long   Lset2
    .byte   1                       ##   On action: 1
Lset3 = Ltmp6-Lfunc_begin0              ## >> Call Site 2 <<
    .long   Lset3
Lset4 = Ltmp7-Ltmp6                     ##   Call between Ltmp6 and Ltmp7
    .long   Lset4
Lset5 = Ltmp8-Lfunc_begin0              ##     jumps to Ltmp8
    .long   Lset5
    .byte   1                       ##   On action: 1
    .byte   1                       ## >> Action Record 1 <<
                                        ##   Catch TypeInfo 1
    .byte   0                       ##   No further actions
                                        ## >> Catch TypeInfos <<
    .long   0                       ## TypeInfo 1
    .align  2

Así que en resumen, nunca use std::exchange para realizar un swap.

 46
Author: Howard Hinnant,
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
2015-05-11 22:15:08

Respuesta corta: no es necesario, pero es útil.

Respuesta Larga:

Uno de los mercados más grandes posibles para C++ es la computación científica y la computación de ingeniería, que está dominada en muchos aspectos por Fortran. Fortran no es exactamente agradable de programar, pero genera resultados superiores debido a varias optimizaciones numéricas que es capaz de hacer. Esta fue una de las principales razones detrás del desarrollo de plantillas de expresión , que permitió a bibliotecas como Blitz++ desarrollar niveles de velocidad cercanos a Fortran (a costa de largos tiempos de compilación y mensajes de error crípticos).

La semántica de movimiento y las plantillas de expresión se desarrollaron para acelerar ciertas áreas de C++, principalmente mediante la eliminación de copias innecesarias y valores temporales. En el caso de la semántica de movimiento, esto aumentó drásticamente la velocidad de los cálculos numéricos básicamente sin costo para el usuario final; una vez que fueron compatibles y se agregaron las semánticas de movimiento predeterminadas a objetos, muchos usos comunes en numéricos se hicieron más rápidos, simplemente permitiendo que las bibliotecas ya presentes dejaran de hacer copias completas en operaciones comunes. Debido al dramático éxito de la semántica del movimiento, otras áreas del lenguaje, tradicionalmente dominadas por expresiones idiomáticas como el copy-and-swap, están siendo vistas bajo una nueva luz y estandarizadas. std:: array es un ejemplo de una de esas reducciones de fuerza; donde, como anteriormente, la mayoría de los escritores estándar habrían dicho " usa vectores, hacen todo lo que quieres y quién se preocupa si son lentos", ahora la llamada es para contenedores más especializados y específicos, como el std::array estático.

Entonces, ¿por qué intercambiar?

Si nos fijamos en boost::swap entenderemos por qué necesitamos el nuevo operador swap: La búsqueda dependiente de argumentos es difícil de encapsular y usar correctamente, y resulta en una explosión de funciones necesarias, donde la idea básica de solo dar una función miembro swap es bastante simple. Tener un operador que puede hacer y proporcionar un operador de intercambio predeterminado que luego se puede usar para una Copia e intercambio predeterminados es un enorme aumento del rendimiento.

¿Por qué? Debido a que std:: swap se define en términos de MoveConstructible y MoveAssignable en C++11 (anteriormente copy construction y copy assignment, en C++98); esto requiere tres movimientos, y un temporal (mucho más rápido que las copias completas necesarias en C++98). Esto es genérico, y bastante rápido, pero no tan rápido como un intercambio personalizado (que puede ser 2-3 veces más rápido, eliminando el temporal y un movimiento en muchos casos). std:: swap también depende de que el tipo sea nothrow-move-constructible y nothrow-move-assignable; es concebible pensar en una clase que no lo es, pero que podría proporcionar garantías de excepción en un swap personalizado, evitando así un comportamiento indefinido.

ADL y std:: swap pueden interactuar muy bien, pero la sintaxis es algo extraña; se agrega

using std::swap;

A tu función llamando a swap, y proporciona una función de amigo gratis como especialización de swap. Reemplazar este extraño caso de esquina ADL implícito con un operador explícito sería más fácil para los ojos, pero como se señaló, parece estar muerto al llegar.

Exchange es una bestia muy similar

Al usar std::move en exchange, ya no es necesaria una copia completa. Mediante el uso de una referencia universal para new_val, el nuevo valor se puede reenviar perfectamente o mover directamente a su nuevo punto. En teoría, el intercambio puede ejecutarse con absolutamente cero copias, solo dos movimientos.

En Resumen

¿por Qué es necesario? Porque es rápido y no impone ningún costo para los usuarios finales, y expande C++ como una alternativa útil a Fortran en computación científica.

 6
Author: Alice,
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
2013-12-30 19:31:08