Подтвердить что ты не робот

Самый эффективный способ возврата переменной reset?

Каков наиболее эффективный способ реализации 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;
}

В настоящее время выполняется, по крайней мере, копия с объектов_ result. Можно ли это сделать быстрее с помощью std::move, например?

4b9b3361

Ответ 1

Вы можете поменять векторы:

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

Ответ 2

Вы можете использовать конструкцию перемещения для типов с поддержкой перемещения, например 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().

Ответ 3

Остальные три ответа верны, поэтому мне нечего добавить здесь, чтобы ответить на вопрос, но поскольку OP заинтересован в эффективности, я собрал все предложения в clang с -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 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 является идиоматически совершенным и оптимально эффективным.

Ответ 4

Идиоматическое выражение должно было бы использовать std::exchange (начиная с С++ 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()));
}

Ответ 5

Обновлено:
Ричард прав. Посмотрите на определение 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;
}