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
.