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

Принимать параметр только для перемещения по значению или значению rvalue

Принятый ответ этого сообщения Передача по значению vs pass by rvalue reference говорит, что:

Для типов только для перемещения (как std::unique_ptr) значение прохода по-умолчанию является нормой...

Я немного сомневаюсь в этом. Скажем, существует некотируемый тип Foo, который также не дешев для перемещения; и некоторый тип Bar, который имеет член Foo.

class Foo {
public:
    Foo(const Foo&) = delete;
    Foo(Foo&&) { /* quite some work */ }
    ...
};

class Bar {
public:
    Bar(Foo f) : f_(std::move(f)) {}    // (1)
    Bar(Foo&& f) : f_(std::move(f)) {}  // (2)
    // Assuming only one of (1) and (2) exists at a time

private:
    Foo f_;
};

Затем для следующего кода:

Foo f;
...
Bar bar(std::move(f));

Конструктор (1) несет 2 перемещения конструкций, а конструктор (2) только берет на себя 1. Я также помню, как читал в Scott Meyers Effective Modern С++ об этом, но не могу сразу вспомнить, какой элемент.

Итак, мой вопрос заключается в том, что для типов только для перемещения (или, что более важно, когда мы хотим передать право собственности на аргумент), не следует ли нам предпочитать ссылку pass-by-rvalue для лучшей производительности?

ОБНОВЛЕНИЕ:. Я знаю, что конструкторы-получатели/присваивания посылок (иногда называемые унифицированными операторами ctors/assign) могут помочь устранить дублированный код. Я должен сказать, что меня больше интересует случай, когда важна производительность (1), и (2) тип не копируется, и поэтому нет дублирующих операторов ctors/assign, которые принимают const аргументы ссылки lvalue.

ОБНОВЛЕНИЕ 2: Итак, я нашел блог Скотта Мейерса о конкретной проблеме: http://scottmeyers.blogspot.com/2014/07/should-move-only-types-ever-be-passed.html. В этом блоге обсуждается причина, по которой он защищает в статье 41 своего Эффективного современного С++, что:

Рассмотрим пропуск по значению только для скопируемых параметров... которые дешевы для перемещения... [и] всегда скопированы.

В этом элементе подробно обсуждается вопрос о передаче по значению vs. rvalue, слишком много для цитирования здесь. Дело в том, что оба способа имеют свои преимущества и недостатки, но для передачи права собственности на объект только для перемещения, рекомендуется использовать ссылку rvalue.

4b9b3361

Ответ 1

В этом случае мы можем получить наш торт и съесть его. Конструктор шаблонов, включенный только для Foo-подобных ссылок, дает нам идеальную пересылку плюс единственную реализацию конструктора:

#include <iostream>
#include <utility>

class Foo {
public:
    Foo() {}
    Foo(const Foo&) = delete;
    Foo(Foo&&) { /* quite some work */ }
};

class Bar {
public:
  template<class T, std::enable_if_t<std::is_same<std::decay_t<T>, Foo>::value>* = nullptr>
    Bar(T&& f) : f_(std::forward<T>(f)) {}  // (2)
    // Assuming only one of (1) and (2) exists at a time

private:
    Foo f_;
};

int main()
{
  Foo f;
  Bar bar(std::move(f));

  // this won't compile
//  Foo f2;
//  Bar bar2(f2);

}

Ответ 2

Фон

Трудно представить класс, который стоит переместить: перемещение семантики происходит именно из необходимости дать быструю альтернативу копиям, когда позволяют семантика.

Вы приводите пример std::string и SSO. Однако этот пример явно ошибочен (я сомневаюсь, что они даже включили оптимизацию), поскольку копирование 16 байтов через memcpy должно занимать кучу циклов ЦП, поскольку оно может быть реализовано в 1 SIMD-инструкции, чтобы хранить их все одновременно. Кроме того, MSVC 10 действительно стар.


Итак, мой вопрос заключается в том, что для типов только для перемещения (или, более общо, когда мы хотите передать право собственности на аргумент), не следует ли нам pass-by-rvalue-reference для лучшей производительности?

Я не буду говорить о производительности, потому что это такой особый аспект и не может быть проанализирован "вообще". Нам нужны конкретные случаи. Также необходимо учитывать оптимизацию компилятора; довольно сильно, на самом деле. И не забывайте тщательный анализ производительности.

std::unique_ptr немного отличается, потому что (i) может перемещаться только из-за владения семантикой (ii) он дешев для перемещения.

Мое мнение. Я бы сказал, что если вам нужно предоставить "более быструю" альтернативу API, предоставляйте оба - точно так же, как std::vector::push_back. Это может иметь, как вы говорите, небольшие улучшения.
В противном случае, даже для типов только для перемещения, передача по const-reference все еще работает, и если вы считаете, что это не так, перейдите для передачи по значению.