Это то, что называется «параметром приемника». Параметр приемника - это параметр метода, который необходимо «взять» у вызывающего объекта и сохранить в объекте (как член данных). Вызывающему обычно не нужен / не используется объект после вызова.
Лучшая практика для параметра приемника - передать его по значению и перейти от него к объекту. Давайте разберемся, почему:
Вариант 1: передать по ссылке
class X; // expensive to copy type with cheap move
struct A
{
X stored_x_;
A(const X& x) : x_{x} {}
// ^~~~~
// this is always a copy
};
В этом случае всегда будет как минимум 1 копия, которую нельзя исключить.
Вариант 2: пройти по значению, а затем перейти от
class X; // expensive to copy type with cheap move
struct A
{
X stored_x_;
A(X x) : x_{std::move(x)} {}
// ^~~~~~~~~~~~~~~~
// this is now a move
};
Мы избавились от перемещения при инициализации A::x_
, но у нас все еще есть копия при передаче параметра, или мы?
Если вызывающий поступает правильно, мы этого не делаем. У нас есть два случая: вызывающей стороне по-прежнему нужна копия переданного объекта (что довольно необычно и не является идиоматическим c). В этом случае да, копия будет сделана, но это потому, что этого требует вызываемый объект, а не из-за недостатка в конструкции нашего класса A
.
Вызывающему объекту не нужен объект после проходя это. В этом случае он перемещает аргумент или, что еще лучше, передает prvalue, и, начиная с C ++ 17 с новыми правилами временной материализации, объект создается непосредственно как параметр:
Передайте xvalue
auto test()
{
X x{};
A a{std::move(x)}; // 2 moves (from arg to parameter and from parameter to `A::x_`)
};
Передайте prvalue
auto test()
{
A a{X{}}; // just the move in the initialization of `A::x_`
}
Вариант 3: перегрузки ссылок lvalue и rvalue
Да, это позволит достичь того же уровня производительности, но зачем использовать 2 перегрузки, если вы можете писать и поддерживать всего 1 метод.
class X; // expensive to copy type with cheap move
struct A
{
X stored_x_;
A(const X& x) : x_{x} {}
A(X&& x) : x_{std::move(x)} {}
};
Ненужная сложность, которая вырывается, когда у вас есть несколько параметров приемника в 1 методе.
Вариант 4: передать по ссылке пересылки:
Опять же, возможно. Но у него могут быть некоторые тонкие, но довольно серьезные проблемы ios:
если у вас нет параметра шаблона, вам нужно сделать его шаблоном, который добавляет сложности, а также добавляет другие проблемы, такие как теперь, метод принимает любой тип .
Это еще хуже для конструктора, поскольку теперь этот конструктор является жизнеспособным вариантом для конструктора копирования, который действительно может напортачить, потому что это лучше подходит для копии из неконстантного объекта.
Другая проблема в том, что его нельзя использовать всегда:
- если вы хотите принять любой тип, который не является просто
T
, например, если X
является шаблоном: template <class T> A(X<T>&& x)
это не ссылка пересылки, а ссылка rvalue, и вам нужна перегрузка ссылки lvalue.