std:: swap vs STD:: exchange vs swap operator

Implementacja std::swap może wyglądać tak:

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]);
}

Implementacja std::exchange n3668 może wyglądać tak:

 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;
   }

Jest napisane:

Dla typów prymitywnych jest to równoznaczne z oczywistą implementacją, natomiast dla bardziej złożonych typów definicja ta

  • unika kopiowania starej wartości, gdy ten typ definiuje konstruktor ruchu
  • akceptuje dowolny typ jako nową wartość, wykorzystując dowolne przypisanie konwersji operator
  • unika kopiowania nowej wartości, jeśli jest tymczasowa lub przeniesiona.

Wybrałem nazwę symetrii z atomic_exchange, ponieważ zachowują się to samo z wyjątkiem tej funkcji, która nie jest atomowa.

N3746 proponuje również wbudowany operator wymiany, który wygląda tak:

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

Z tego, co wiem, propozycje te chciałyby, aby wszystkie trzy z tych opcji żyły obok siebie, a nie zastępowały się nawzajem. Dlaczego konieczne jest posiadanie trzy różne sposoby wymiany obiektów?

Author: Howard Hinnant, 2013-12-28

2 answers

Std:: swap vs std:: exchange

swap(x, y) i {[8] } to nie to samo. exchange(x, y) nigdy nie przypisuje nowej wartości y. Możesz to zrobić, jeśli użyjesz go w ten sposób: y = exchange(x, y). Ale to nie jest główny przypadek użycia exchange(x, y). N3668 zawiera stwierdzenie:

Korzyści nie są ogromne, ale nie jest to koszt specyfikacji.

(w odniesieniu do standaryzacji exchange).

N3668 został wybrany do projektu roboczego C++1y na spotkaniu w Bristolu, kwiecień 2013. Protokół z posiedzenia wskazuje, że w Grupie Roboczej ds. bibliotek odbyła się dyskusja na temat najlepszej nazwy tej funkcji i ostatecznie nie zgłoszono sprzeciwu wobec poddania jej pod formalne głosowanie w pełnej Komisji. Oficjalne głosowanie było zdecydowanie za wprowadzeniem go do projektu roboczego, ale nie jednomyślne.

Podsumowując: exchange jest niewielkim narzędziem, nie konkuruje z {[7] } i ma znacznie mniej przypadków użycia.

Std:: swap vs operator swap

N3553, poprzedni przegląd N3746 , został omówiony w Grupie Roboczej ds. ewolucji na posiedzeniu w kwietniu 2013 r.w Bristolu. Protokół spotkania potwierdza "irytujące problemy ADL" z std::swap(x, y), ale stwierdza, że operator swap nie rozwiązałby tych problemów. Ze względu na wsteczną kompatybilność, EWG wierzyła również, że jeśli zostanie zaakceptowana, std::swap i operator swap będą na zawsze współistnieć. EWG postanowiła w Bristolu nie kontynuować N3553 .

The Sep. 2013 Chicago EWG meeting minutes nie wspomina N3746 . Nie byłem obecny na tym spotkaniu, ale zakładam, że EWG odmówiła przyjrzenia się N3746 Z powodu swojej poprzedniej decyzji w Bristolu w sprawie N3553.

Podsumowując: Komitet C++ nie wydaje się w tej chwili posuwać się naprzód z operatorem swap.

Aktualizacja: czy std:: exchange może być szybszy niż std::swap?

Podgląd: Nie. W najlepszym razie {[13] } będzie tak samo szybki jak swap. W najgorszym przypadku może być wolniejszy.

Rozważ taki test:

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));
}

Który generuje szybszy kod?

Clang-O3 generuje identyczny kod (poza zniekształconymi nazwami funkcji):]}
__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

Dla dowolnego typu X, który nie posiada wyspecjalizowanej funkcji swap, oba testy wygenerują jedno wywołanie X(X&&) (zakładając, że członkowie move istnieją dla X) i dwa wywołania 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

Znowu prawie ten sam kod.

Ale dla typów, które mają zoptymalizowany swap, test_swap prawdopodobnie wygeneruje znacznie lepszy kod. Rozważyć:

using T = std::string;

(using 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

Podsumowując, nigdy nie używaj std::exchange do wykonywania 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/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-11 22:15:08

Krótka odpowiedź: nie jest to konieczne, ale przydatne.

Długa odpowiedź :

Jednym z największych możliwych rynków dla C++ są obliczenia naukowe i obliczenia inżynierskie, które są zdominowane pod wieloma względami przez Fortran. Fortran nie jest do końca przyjemny w programowaniu, ale generuje lepsze wyniki dzięki różnym optymalizacjom numerycznym, do których jest zdolny. Był to jeden z głównych powodów rozwoju szablonów wyrażeń, które pozwalał bibliotekom takim jak Blitz++ rozwijać prędkości zbliżone do Fortran (kosztem długich czasów kompilacji i tajemniczych komunikatów o błędach).

Semantyka Move i szablony wyrażeń zostały opracowane w celu przyspieszenia pewnych obszarów C++, głównie poprzez wyeliminowanie zbędnych kopii i tymczasowych wartości. W przypadku semantyki move, to drastycznie zwiększyło szybkość obliczeń numerycznych w zasadzie bez kosztów dla użytkownika końcowego; po ich obsłudze i domyślnej semantyki move zostały dodane do obiekty, wiele powszechnych zastosowań w numeryce stało się szybsze, po prostu pozwalając już obecnym bibliotekom przestać wykonywać pełne kopie na zwykłych operacjach. Ze względu na dramatyczny sukces semantyki move, inne obszary języka, tradycyjnie zdominowane przez idiomy, takie jak copy-and-swap, są postrzegane w nowym świetle i standaryzowane. std:: array jest przykładem takiej redukcji siły; gdzie jak poprzednio większość standardowych pisarzy powiedziałaby " użyj wektorów, robią wszystko, co chcesz i kto cares if they are slow", teraz wywołanie jest dla bardziej wyspecjalizowanych i specyficznych kontenerów, takich jak static STD:: array.

Więc po co się wymieniać?

Jeśli spojrzysz na boost:: swap zrozumiesz, dlaczego mamy potrzebę nowego operatora swap: Wyszukiwanie zależne od argumentów jest trudne do hermetyzacji i poprawnego użycia, a skutkuje eksplozją potrzebnych funkcji, gdzie podstawowa idea podania funkcji swap member jest dość prosta. Posiadanie operatora, który potrafi to, i zapewnienie domyślnego operatora swapu, który może być następnie używany do domyślnej kopii i Swapu, jest ogromnym wzrostem wydajności.

Dlaczego? Ponieważ std:: swap jest zdefiniowany w kategoriach MoveConstructible i MoveAssignable w C++11 (dawniej copy construction i copy assignable, w C++98); wymaga to trzech ruchów i tymczasowego (znacznie szybszego niż pełne kopie potrzebne w C++98). Jest to ogólny i dość szybki, ale nie tak szybki jak niestandardowy swap (który może być 2-3x szybszy, usuwając tymczasowe i jeden ruch w wielu przypadkach). std:: swap zależy również od typu nothrow-move-constructible i nothrow-move-assignable; można pomyśleć o klasie, która nie jest, ale która mogłaby zapewnić gwarancje wyjątków dla niestandardowego swapu, unikając w ten sposób niezdefiniowanego zachowania.

ADL i std:: swap mogą współdziałać bardzo ładnie, ale składnia jest nieco dziwna; dodajesz

using std::swap;

Do funkcji wywołującej swap, oraz udostępnić funkcję Free friend jako specjalizację swap. Zastąpienie tego dziwnego ukrytego przypadku ADL corner wyraźnym operatorem byłoby łatwiejsze dla oczu, ale jak zauważono, wydaje się być martwy po przybyciu.

Exchange to bardzo podobna bestia

Używając STD:: move W exchange, pełna kopia nie jest już potrzebna. Używając uniwersalnego odniesienia dla new_val, nowa wartość może być idealnie przekazana lub przeniesiona bezpośrednio do nowego miejsca. Teoretycznie exchange może działać z absolutnie zerowymi kopiami, tylko dwoma ruchami.

W Podsumowanie

Dlaczego jest to konieczne? Ponieważ jest szybki i nie nakłada żadnych kosztów na użytkowników końcowych, i rozszerza C++ jako użyteczną alternatywę dla Fortran w komputerach naukowych.
 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/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-30 19:31:08