返回+重置成员变量的最有效方法

Most efficient way to return+reset member variable?

本文关键字:有效 方法 变量 成员 返回      更新时间:2023-10-16

下面实现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_ left in valid but unspecified state after move
     objects_.clear();
     return result;
}

在许多实现中,移动构造期间的传输很可能已经重置了指针,并且不需要对clear()的调用。但是,从中移出的对象只能保证处于有效但未指定的状态。因此,不幸的是,它对于clear()是必要的。

其他三个答案都是正确的,所以在回答这个问题方面我没有什么要补充的,但由于OP对效率感兴趣,我用-O3汇编了所有建议。

这两种解决方案之间几乎没有任何区别,但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    $0, 8(%rdi)          ; construct tmp's allocator
    movq    $0, (%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    $0, 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    $0, 8(%rdi)         ; move-construct ... shame about these
    movq    $0, (%rdi)          ; ... redundant zero-writes
    movups  (%rsi), %xmm0       ; ... copy right to left ...
    movups  %xmm0, (%rdi)
    movq    16(%rsi), %rax
    movq    %rax, 16(%rdi)
    movq    $0, 16(%rsi)      ; zero out moved-from vector ...
    movq    $0, 8(%rsi)       ; ... happens to be identical to clear()
    movq    $0, (%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    $0, (%rdi)            ; move-construct the result
    movq    (%rsi), %rax
    movq    %rax, (%rdi)
    movups  8(%rsi), %xmm0
    movups  %xmm0, 8(%rdi)
    movq    $0, 16(%rsi)          ; zero out the source
    movq    $0, 8(%rsi)
    movq    $0, (%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()));
}

更新时间:
理查德是对的。在看了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;
}