28 votes

Le moyen le plus efficace de renvoyer + réinitialiser la variable membre?

Quel est le moyen le plus efficace pour implémenter GetDeleteObjects ci-dessous?

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

Actuellement, au moins la copie des objets_ vers le résultat est exécutée. Cela peut-il être fait plus rapidement avec std::move , par exemple?

32voto

40two Points 8224

Vous pouvez échanger les vecteurs:

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

16voto

Dietmar Kühl Points 70604

Vous pouvez utiliser la construction de déplacement pour les types sensibles aux déplacements comme 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;
}
 

Le transfert pendant les mouvements de construction réinitialise très probablement déjà les pointeurs et le clear() ne fera rien. Puisqu'il n'y a aucune garantie dans quel état un objet déplacé est, il est malheureusement nécessaire de clear() .

12voto

Richard Hodges Points 1972

Les trois autres réponses sont correctes, il n'y a rien pour moi d'ajouter ici en termes de réponse à la question, mais depuis l'OP est intéressée à l'efficacité j'ai compilé toutes les suggestions dans clang avec-O3.

Il n'y a presque rien entre les deux solutions, mais l' std::exchange solution se distingue comme la production de code plus efficace sur mon compilateur, avec l'avantage supplémentaire qu'il est idiomatique parfait.

Je pensais que les résultats étaient intéressants:

donnée:

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

résultats:

__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

donnée:

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

résultats:

__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

enfin, compte tenu :

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

résultats dans un très agréable:

__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:

Le std::méthode d'échange est à la fois idiomatique parfait et parfaitement efficace.

7voto

ecatmur Points 64173

L'expression idiomatique serait d'utiliser std::exchange (depuis C ++ 14):

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

Notez que cela suppose que l'attribution d'une valeur initialisée vector équivaut à appeler clear ; ce sera le cas sauf si vous utilisez des allocateurs avec état avec propagate_on_container_move_assignment , auquel cas vous souhaitez réutiliser explicitement l'allocateur:

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

-1voto

Vincent Pang Points 34

Mis à jour:
Richard a raison. Après avoir regardé la définition de std :: move , il traite du pointeur au lieu de la valeur réelle, ce qui est plus intelligent que ma pensée. La technique ci-dessous est donc obsolète.

Ancien (obsolète):
Vous pouvez utiliser des pointeurs

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by: