Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Most efficient way to return+reset member variable?

Whats the most efficient way to implement GetDeleteObjects below?

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

Currently, at least the copy from objects_ to result is executed. Can this be made faster with std::move, for example?

like image 361
Jan Rüegg Avatar asked Dec 15 '15 09:12

Jan Rüegg


3 Answers

You could swap the vectors:

std::vector<Bar> Foo::GetDeleteObjects() {   std::vector<Bar> result;   result.swap(objects_);   return result; } 
like image 112
101010 Avatar answered Sep 20 '22 17:09

101010


You can use move construction for move-aware types like 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;
}

In many implementations, the transfer during the move-constructions most likely already resets the pointers and the call to clear() is not needed. However, a moved from object is only guaranteed to be in a valid but unspecified state. Thus, it is, unfortunately, necessary to clear().

like image 38
Dietmar Kühl Avatar answered Sep 21 '22 17:09

Dietmar Kühl


The other three answers are correct so there is nothing for me to add here in terms of answering the question, but since the OP is interested in efficiency I compiled up all suggestions in clang with -O3.

There's almost nothing in it between two of the solutions, but the std::exchange solution stands out as producing more efficient code on my compiler, with the added advantage that it's idiomatically perfect.

I thought the results were interesting:

given:

std::vector<Bar> Foo::GetDeleteObjects1() {
    std::vector<Bar> tmp;
    tmp.swap(objects_);
    return tmp;
}

results in:

__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

given:

std::vector<Bar>
Foo::GetDeleteObjects2() {
    std::vector<Bar> tmp = std::move(objects_);
    objects_.clear();
    return tmp;
}

results in:

__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

finally, given :

std::vector<Bar>
Foo::GetDeleteObjects3() {
    return std::exchange(objects_, {});
}

results in a very pleasing:

__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

Conclusion:

The std::exchange method is both idiomatically perfect and optimally efficient.

like image 42
Richard Hodges Avatar answered Sep 18 '22 17:09

Richard Hodges