spanish english business c++ swap c++14

c++ - business - financial glossary english spanish pdf



std:: swap vs std:: operador de intercambio vs cambio (2)

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 los tipos más complejos, esta definición

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

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

n3746 también propone un operador de intercambio integrado que se ve así:

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

Según lo que deduzco, a las propuestas les gustaría que las tres opciones vivieran lado a lado, en lugar de reemplazarse entre sí. ¿Por qué es necesario tener tres formas diferentes de intercambiar objetos?


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

Larga respuesta :

Uno de los mayores mercados posibles para C ++ son los cálculos científicos y los cálculos de ingeniería, que está dominado de muchas maneras por Fortran. Fortran no es exactamente agradable de programar, pero genera resultados superiores debido a varias optimizaciones numéricas que es capaz de realizar. Esta fue una de las principales razones detrás del desarrollo de plantillas de expresión , lo que permitió que bibliotecas como Blitz ++ desarrollaran 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 eliminando copias innecesarias y valores temporales. En el caso de la semántica de movimientos, 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 la semántica de movimiento predeterminada se agregó a los objetos, muchos usos comunes en numéricos se volvieron más rápidos, simplemente al permitir que las bibliotecas ya presentes dejen de hacer copias completas en operaciones comunes. Debido al éxito dramático de la semántica de movimientos, otras áreas del lenguaje, tradicionalmente dominadas por expresiones idiomáticas tales como copiar y cambiar, están siendo vistas bajo una nueva luz y estandarizadas. std :: array es un ejemplo de una de esas reducciones de potencia; mientras que la mayoría de los escritores estándar habrían dicho "usar vectores, hacen todo lo que quieren y a quién le importa si son lentos", ahora la llamada es para contenedores más especializados y específicos, como el std :: array estático.

Entonces, ¿por qué cambiar?

Si observa boost::swap comprenderá por qué necesitamos el nuevo operador de intercambio: Argument Dependent Lookup es difícil de encapsular y usar correctamente, y da como resultado una explosión de las funciones necesarias, donde la idea básica de simplemente dar una función de miembro de intercambio es bastante simple. Tener un operador que pueda hacerlo, y proporcionar un operador de intercambio predeterminado que luego se pueda usar para un Copy & Swap predeterminado es un impulso enorme para el rendimiento.

¿Por qué? Porque std :: swap se define en términos de MoveConstructible y MoveAssignable en C ++ 11 (anteriormente construcción de copia y asignación de copias, 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 intercambio personalizado, evitando así un comportamiento indefinido.

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

using std::swap;

a su función de llamada de intercambio, y proporcionar una función de amigo libre como una especialización de intercambio. Reemplazar este extraño estuche 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 a cambio, ya no es necesaria una copia completa. Al usar una referencia universal para new_val, el nuevo valor se puede reenviar o mover directamente a su nuevo lugar. En teoría, el intercambio puede ejecutarse con absolutamente cero copias, solo dos movimientos.

En resumen

¿Por qué es necesario? Porque es rápido e impone ningún costo para los usuarios finales, y amplía C ++ como una alternativa útil a Fortran en la informática científica.


std :: swap vs std :: intercambio

swap(x, y) e 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 el 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 estandarización del 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 para una votación formal en el comité completo. El voto formal estuvo fuertemente a favor de incluirlo en el borrador de trabajo, pero no unánime.

En pocas palabras: el exchange es una utilidad menor, no compite con swap(x, y) , y tiene muchos menos casos de uso.

std :: swap vs swap operator

N3553 , una revisión previa a n3746 , se discutió en el Grupo de Trabajo de Evolución en la reunión de abril de 2013 en Bristol. Las minutas de la reunión reconocen "molestos problemas de ADL" con std::swap(x, y) , pero concluyen que un operador de intercambio no resolvería esos problemas. Debido a la compatibilidad con versiones anteriores, 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 .

Las minutas de la reunión del EWG de Chicago de septiembre de 2013 no mencionan el n3746 . No estuve presente en esa reunión, pero supongo que el EWG se negó a mirar n3746 debido a su decisión anterior en Bristol en N3553 .

En pocas palabras: el comité 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 exchange casos, el exchange será tan rápido como el 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 destrozados 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 de swap especializada, ambas pruebas generarán una llamada a X(X&&) (suponiendo que los miembros de movimiento existan para X ), y dos llamadas a 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 los tipos que tienen un swap optimizado, test_swap es probable que genere un código muy superior. Considerar:

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

Entonces, en resumen, nunca use std::exchange para realizar un swap .