Ответ 1
Вы можете поменять векторы:
std::vector<Bar>
Foo::GetDeleteObjects() {
std::vector<Bar> result;
result.swap(objects_);
return result;
}
Каков наиболее эффективный способ реализации 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
, например?
Вы можете поменять векторы:
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()
.
Остальные три ответа верны, поэтому мне нечего добавить здесь, чтобы ответить на вопрос, но поскольку 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 является идиоматически совершенным и оптимально эффективным.
Идиоматическое выражение должно было бы использовать 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()));
}
Обновлено:
Ричард прав. Посмотрите на определение 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;
}