gpt4 book ai didi

c++ - std::swap vs std::exchange vs 交换运算符

转载 作者:IT老高 更新时间:2023-10-28 14:02:06 34 4
gpt4 key购买 nike

std::swap 的实现可能如下所示:

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

一个实现std::exchange n3668可能看起来像这样:

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

上面写着:

For primitive types, this is equivalent to the obvious implementation, while for more complex types, this definition

  • Avoids copying the old value when that type defines a move constructor
  • Accepts any type as the new value, taking advantage of any converting assignment operator
  • Avoids copying the new value if it's a temporary or moved.

I chose the name for symmetry with atomic_exchange, since they behave the same except for this function not being atomic.

n3746还提出了一个内置的交换运算符,如下所示:

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

据我所知,这些提案希望所有这三个选项并存,而不是相互替换。为什么需要三种不同的方式来交换对象?

最佳答案

std::swap 与 std::exchange

swap(x, y)exchange(x, y) 不是一回事。 exchange(x, y) 永远不会为 y 分配新值。如果你像这样使用它,你可以这样做:y = exchange(x, y)。但这不是 exchange(x, y) 的主要用例。 N3668包括声明:

The benefit isn't huge, but neither is the specification cost.

(关于标准化exchange)。

N3668在 2013 年 4 月的布里斯托尔 session 上被投票通过 C++1y 工作草案。 session 纪要表明,在图书馆工作组中对该函数的最佳名称进行了一些讨论,最终没有反对将其放入在全体委员会中进行正式投票。正式投票强烈赞成将其纳入工作草案,但并非一致。

底线:exchange 是一个次要实用程序,不与 swap(x, y) 竞争,用例也少得多。

std::swap 与交换运算符

N3553 , 对 N3746 的先前修订,进化工作组在 2013 年 4 月的布里斯托尔 session 上进行了讨论。 session 纪要承认 std::swap(x, y) 的“烦人的 ADL 问题”,但得出的结论是交换运算符(operator)不会解决这些问题。由于向后兼容,EWG 还认为如果被接受,std::swap 和交换运算符将永远共存。 EWG 在布里斯托尔决定不进行 N3553 .

2013 年 9 月芝加哥 EWG session 记录没有提及 N3746 .我没有出席那次 session ,但推测 EWG 拒绝查看 N3746因为它之前在布里斯托尔的决定 N3553 .

底线:C++ 委员会目前似乎没有使用交换运算符。

更新:std::exchange 能比 std::swap 快吗?

Preview:不会。充其量 exchange 将与 swap 一样快。在最坏的情况下,它可能会更慢。

考虑这样的测试:

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

哪个生成更快的代码?

使用 clang -O3,它们都生成相同的代码(除了函数名称困惑):

__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

对于一些任意类型的 X,它没有专门的 swap 函数,两个测试都会生成一个对 X(X&&) 的调用(假设 X 存在 move 成员),并且两次调用 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

又是几乎相同的代码。

但是对于具有优化的 swap 的类型,test_swap 可能会生成更出色的代码。考虑:

using T = std::string;

(使用 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

总而言之,永远不要使用 std::exchange 来执行 swap

关于c++ - std::swap vs std::exchange vs 交换运算符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20807938/

34 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com