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?
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
.
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.
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