return+重置成员变量的最有效方法?
Most efficient way to return+reset member variable?
实施以下 GetDeleteObjects
最有效的方法是什么?
class Foo {
public:
std::vector<Bar> GetDeleteObjects();
private:
std::vector<Bar> objects_;
}
std::vector<Bar> Foo::GetDeleteObjects() {
std::vector<Bar> result = objects_;
objects_.clear();
return result;
}
目前,至少执行了从objects_ 到result 的复制。例如,可以使用 std::move
来加快速度吗?
你可以交换向量:
std::vector<Bar>
Foo::GetDeleteObjects() {
std::vector<Bar> result;
result.swap(objects_);
return result;
}
您可以对移动感知类型使用移动构造,例如 std::vector<T>
:
std::vector<Bar>
Foo::GetDeleteObjects() {
std::vector<Bar> result(std::move(objects_));
objects_.clear(); // objects_ left in unspecified state after move
return result;
}
移动构造期间的传输很可能已经重置了指针,clear()
不会做任何事情。由于无法保证从对象移出的状态是什么,不幸的是,有必要 clear()
.
已更新:
理查德是对的。看了std::move的定义后,它处理的是指针而不是实际值,这比我想象的要聪明。所以下面的技术已经过时了。
旧(过时):
你可以使用指针
class Foo {
public:
Foo();
std::vector<Bar> GetDeleteObjects();
private:
std::vector<Bar> objects1_;
std::vector<Bar> objects2_;
std::vector<Bar> *currentObjects_;
std::vector<Bar> *deletedObjects_;
}
Foo::Foo() :
currentObjects_(&objects1_)
, deletedObjects_(&objects2_)
{
}
Foo::GetDeleteObjects() {
deletedObjects_->clear();
std::swap(currentObjects_, deletedObjects_);
return *deletedObjects;
}
其他三个答案是正确的,所以在回答问题方面我没有什么可以补充的,但是由于 OP 对效率感兴趣,我用 -O3 在 clang 中编译了所有建议。
两个解决方案之间几乎没有任何区别,但 std::exchange
解决方案脱颖而出,因为它可以在我的编译器上生成更高效的代码,并且具有完美的惯用优势。
我认为结果很有趣:
给定:
std::vector<Bar> Foo::GetDeleteObjects1() {
std::vector<Bar> tmp;
tmp.swap(objects_);
return tmp;
}
结果:
__ZN3Foo17GetDeleteObjects1Ev:
.cfi_startproc
pushq %rbp
Ltmp0:
.cfi_def_cfa_offset 16
Ltmp1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp2:
.cfi_def_cfa_register %rbp
movq [=11=], 8(%rdi) ; construct tmp's allocator
movq [=11=], (%rdi) ;... shame this wasn't optimised away
movups (%rsi), %xmm0 ; swap
movups %xmm0, (%rdi)
xorps %xmm0, %xmm0 ;... but compiler has detected that
movups %xmm0, (%rsi) ;... LHS of swap will always be empty
movq 16(%rsi), %rax ;... so redundant fetch of LHS is elided
movq %rax, 16(%rdi)
movq [=11=], 16(%rsi) ;... same here
movq %rdi, %rax
popq %rbp
retq
给定:
std::vector<Bar>
Foo::GetDeleteObjects2() {
std::vector<Bar> tmp = std::move(objects_);
objects_.clear();
return tmp;
}
结果:
__ZN3Foo17GetDeleteObjects2Ev:
.cfi_startproc
pushq %rbp
Ltmp3:
.cfi_def_cfa_offset 16
Ltmp4:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp5:
.cfi_def_cfa_register %rbp
movq [=13=], 8(%rdi) ; move-construct ... shame about these
movq [=13=], (%rdi) ; ... redundant zero-writes
movups (%rsi), %xmm0 ; ... copy right to left ...
movups %xmm0, (%rdi)
movq 16(%rsi), %rax
movq %rax, 16(%rdi)
movq [=13=], 16(%rsi) ; zero out moved-from vector ...
movq [=13=], 8(%rsi) ; ... happens to be identical to clear()
movq [=13=], (%rsi) ; ... so clear() is optimised away
movq %rdi, %rax
popq %rbp
retq
最后给出:
std::vector<Bar>
Foo::GetDeleteObjects3() {
return std::exchange(objects_, {});
}
结果非常令人满意:
__ZN3Foo17GetDeleteObjects3Ev:
.cfi_startproc
pushq %rbp
Ltmp6:
.cfi_def_cfa_offset 16
Ltmp7:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp8:
.cfi_def_cfa_register %rbp
movq [=15=], (%rdi) ; move-construct the result
movq (%rsi), %rax
movq %rax, (%rdi)
movups 8(%rsi), %xmm0
movups %xmm0, 8(%rdi)
movq [=15=], 16(%rsi) ; zero out the source
movq [=15=], 8(%rsi)
movq [=15=], (%rsi)
movq %rdi, %rax
popq %rbp
retq
结论:
std::exchange 方法既完美又高效。
惯用表达式是使用 std::exchange
(C++14 起):
std::vector<Bar> Foo::GetDeleteObjects() {
return std::exchange(objects_, {});
}
注意这里假设赋值初始化的vector
等同于调用clear
;除非您将有状态分配器与 propagate_on_container_move_assignment
一起使用,否则情况就是如此,在这种情况下,您希望明确地重用分配器:
std::vector<Bar> Foo::GetDeleteObjects() {
return std::exchange(objects_, std::vector<Bar>(objects_.get_allocator()));
}
实施以下 GetDeleteObjects
最有效的方法是什么?
class Foo {
public:
std::vector<Bar> GetDeleteObjects();
private:
std::vector<Bar> objects_;
}
std::vector<Bar> Foo::GetDeleteObjects() {
std::vector<Bar> result = objects_;
objects_.clear();
return result;
}
目前,至少执行了从objects_ 到result 的复制。例如,可以使用 std::move
来加快速度吗?
你可以交换向量:
std::vector<Bar>
Foo::GetDeleteObjects() {
std::vector<Bar> result;
result.swap(objects_);
return result;
}
您可以对移动感知类型使用移动构造,例如 std::vector<T>
:
std::vector<Bar>
Foo::GetDeleteObjects() {
std::vector<Bar> result(std::move(objects_));
objects_.clear(); // objects_ left in unspecified state after move
return result;
}
移动构造期间的传输很可能已经重置了指针,clear()
不会做任何事情。由于无法保证从对象移出的状态是什么,不幸的是,有必要 clear()
.
已更新:
理查德是对的。看了std::move的定义后,它处理的是指针而不是实际值,这比我想象的要聪明。所以下面的技术已经过时了。
旧(过时):
你可以使用指针
class Foo {
public:
Foo();
std::vector<Bar> GetDeleteObjects();
private:
std::vector<Bar> objects1_;
std::vector<Bar> objects2_;
std::vector<Bar> *currentObjects_;
std::vector<Bar> *deletedObjects_;
}
Foo::Foo() :
currentObjects_(&objects1_)
, deletedObjects_(&objects2_)
{
}
Foo::GetDeleteObjects() {
deletedObjects_->clear();
std::swap(currentObjects_, deletedObjects_);
return *deletedObjects;
}
其他三个答案是正确的,所以在回答问题方面我没有什么可以补充的,但是由于 OP 对效率感兴趣,我用 -O3 在 clang 中编译了所有建议。
两个解决方案之间几乎没有任何区别,但 std::exchange
解决方案脱颖而出,因为它可以在我的编译器上生成更高效的代码,并且具有完美的惯用优势。
我认为结果很有趣:
给定:
std::vector<Bar> Foo::GetDeleteObjects1() {
std::vector<Bar> tmp;
tmp.swap(objects_);
return tmp;
}
结果:
__ZN3Foo17GetDeleteObjects1Ev:
.cfi_startproc
pushq %rbp
Ltmp0:
.cfi_def_cfa_offset 16
Ltmp1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp2:
.cfi_def_cfa_register %rbp
movq [=11=], 8(%rdi) ; construct tmp's allocator
movq [=11=], (%rdi) ;... shame this wasn't optimised away
movups (%rsi), %xmm0 ; swap
movups %xmm0, (%rdi)
xorps %xmm0, %xmm0 ;... but compiler has detected that
movups %xmm0, (%rsi) ;... LHS of swap will always be empty
movq 16(%rsi), %rax ;... so redundant fetch of LHS is elided
movq %rax, 16(%rdi)
movq [=11=], 16(%rsi) ;... same here
movq %rdi, %rax
popq %rbp
retq
给定:
std::vector<Bar>
Foo::GetDeleteObjects2() {
std::vector<Bar> tmp = std::move(objects_);
objects_.clear();
return tmp;
}
结果:
__ZN3Foo17GetDeleteObjects2Ev:
.cfi_startproc
pushq %rbp
Ltmp3:
.cfi_def_cfa_offset 16
Ltmp4:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp5:
.cfi_def_cfa_register %rbp
movq [=13=], 8(%rdi) ; move-construct ... shame about these
movq [=13=], (%rdi) ; ... redundant zero-writes
movups (%rsi), %xmm0 ; ... copy right to left ...
movups %xmm0, (%rdi)
movq 16(%rsi), %rax
movq %rax, 16(%rdi)
movq [=13=], 16(%rsi) ; zero out moved-from vector ...
movq [=13=], 8(%rsi) ; ... happens to be identical to clear()
movq [=13=], (%rsi) ; ... so clear() is optimised away
movq %rdi, %rax
popq %rbp
retq
最后给出:
std::vector<Bar>
Foo::GetDeleteObjects3() {
return std::exchange(objects_, {});
}
结果非常令人满意:
__ZN3Foo17GetDeleteObjects3Ev:
.cfi_startproc
pushq %rbp
Ltmp6:
.cfi_def_cfa_offset 16
Ltmp7:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp8:
.cfi_def_cfa_register %rbp
movq [=15=], (%rdi) ; move-construct the result
movq (%rsi), %rax
movq %rax, (%rdi)
movups 8(%rsi), %xmm0
movups %xmm0, 8(%rdi)
movq [=15=], 16(%rsi) ; zero out the source
movq [=15=], 8(%rsi)
movq [=15=], (%rsi)
movq %rdi, %rax
popq %rbp
retq
结论:
std::exchange 方法既完美又高效。
惯用表达式是使用 std::exchange
(C++14 起):
std::vector<Bar> Foo::GetDeleteObjects() {
return std::exchange(objects_, {});
}
注意这里假设赋值初始化的vector
等同于调用clear
;除非您将有状态分配器与 propagate_on_container_move_assignment
一起使用,否则情况就是如此,在这种情况下,您希望明确地重用分配器:
std::vector<Bar> Foo::GetDeleteObjects() {
return std::exchange(objects_, std::vector<Bar>(objects_.get_allocator()));
}