САМЫЙ ВАЖНЫЙ ВОПРОС ПЕРВЫЙ:
Есть ли лучшие практики отправки параметров на C ++, потому что я действительно считаю это, скажем так, нетривиальным
Если вашей функции необходимо изменить исходный передаваемый объект, чтобы после возврата вызова изменения этого объекта были видны вызывающему, тогда вы должны передать ссылку lvalue :
void foo(my_class& obj)
{
// Modify obj here...
}
Если вашей функции не нужно изменять исходный объект и не нужно создавать его копию (другими словами, ей нужно только наблюдать за его состоянием), тогда вы должны передать ссылку lvalue наconst :
void foo(my_class const& obj)
{
// Observe obj here
}
Это позволит вам вызывать функцию как с lvalues (lvalues - это объекты со стабильной идентичностью), так и с rvalues (rvalue - это, например, временные объекты или объекты, от которых вы собираетесь перейти в результате вызова std::move()).
Можно также утверждать, что для основных типов или видов , для которых копирование быстро , например int, boolили char, нет необходимости передавать по ссылке , если функции просто необходимо соблюдать значение, и передачи по значению следует отдавать предпочтение . Это правильно, если ссылочная семантика не требуется, но что, если функция хочет где-то сохранить указатель на тот же самый входной объект, чтобы при дальнейшем чтении этого указателя были видны изменения значений, которые были выполнены в какой-то другой части код? В этом случае передача по ссылке - правильное решение.
Если ваша функция не требуется изменять исходный объект, но необходимо сохранить копию этого объекта ( возможно, чтобы вернуть результат преобразования ввода без изменения ввода ), тогда вы можете рассмотреть возможность выбора по значению :
void foo(my_class obj) // One copy or one move here, but not working on
// the original object...
{
// Working on obj...
// Possibly move from obj if the result has to be stored somewhere...
}
Вызов вышеуказанной функции всегда будет приводить к одной копии при передаче lvalues и к одной копии при передаче rvalue. Если функция должна хранить этот объект где - то, вы могли бы выполнить дополнительный шаг от него (например, в случае , foo()является функцией - членом , который должен хранить значение в элементе данных ).
Если ходы дорогие для объектов типа my_class, вы можете рассмотреть вопрос о перегрузке foo()и предоставить одну версию для lvalue (принимая ссылку lvalue на const) и одну версию для rvalue (принимая ссылку rvalue):
// Overload for lvalues
void foo(my_class const& obj) // No copy, no move (just reference binding)
{
my_class copyOfObj = obj; // Copy!
// Working on copyOfObj...
}
// Overload for rvalues
void foo(my_class&& obj) // No copy, no move (just reference binding)
{
my_class copyOfObj = std::move(obj); // Move!
// Notice, that invoking std::move() is
// necessary here, because obj is an
// *lvalue*, even though its type is
// "rvalue reference to my_class".
// Working on copyOfObj...
}
Фактически, указанные выше функции настолько похожи, что вы можете сделать из них одну-единственную функцию: foo()может стать шаблоном функции и вы могли бы использовать идеальную пересылку для определения того, будет ли внутреннее сгенерировано перемещение или копия переданного объекта:
template<typename C>
void foo(C&& obj) // No copy, no move (just reference binding)
// ^^^
// Beware, this is not always an rvalue reference! This will "magically"
// resolve into my_class& if an lvalue is passed, and my_class&& if an
// rvalue is passed
{
my_class copyOfObj = std::forward<C>(obj); // Copy if lvalue, move if rvalue
// Working on copyOfObj...
}
Вы можете узнать больше об этом дизайне, посмотрев доклад Скотта Мейерса (помните, что термин « универсальные ссылки », который он использует, нестандартен).
Следует иметь в виду, что std::forward обычно это заканчивается перемещением для rvalues, поэтому, даже если это выглядит относительно невинно, пересылка одного и того же объекта несколько раз может быть источником проблем - например, перемещение от одного и того же объекта дважды! Так что будьте осторожны, чтобы не поместить это в цикл и не пересылать один и тот же аргумент несколько раз в вызове функции:
template<typename C>
void foo(C&& obj)
{
bar(std::forward<C>(obj), std::forward<C>(obj)); // Dangerous!
}
Также обратите внимание, что вы обычно не прибегаете к решению на основе шаблонов, если у вас нет для этого веской причины, поскольку это затрудняет чтение вашего кода. Обычно вам следует сосредоточиться на ясности и простоте .
Вышеупомянутое - всего лишь простые рекомендации, но в большинстве случаев они укажут вам на хорошие дизайнерские решения.
ОТНОСИТЕЛЬНО ОТДЫХА ВАШЕГО ПОЧТА:
Если я перепишу его как [...], будет 2 хода и не будет копии.
Это не так. Начнем с того, что ссылка rvalue не может быть привязана к lvalue, поэтому она будет компилироваться только тогда, когда вы передаете rvalue типа CreditCardвашему конструктору. Например:
// Here you are passing a temporary (OK! temporaries are rvalues)
Account acc("asdasd",345, CreditCard("12345",2,2015,1001));
CreditCard cc("12345",2,2015,1001);
// Here you are passing the result of std::move (OK! that's also an rvalue)
Account acc("asdasd",345, std::move(cc));
Но это не сработает, если вы попытаетесь это сделать:
CreditCard cc("12345",2,2015,1001);
Account acc("asdasd",345, cc); // ERROR! cc is an lvalue
Поскольку ccэто lvalue, а ссылки rvalue не могут связываться с lvalue. Более того, при привязке ссылки к объекту перемещение не выполняется : это просто привязка ссылки. Таким образом, ход будет только один .
Итак, основываясь на рекомендациях, приведенных в первой части этого ответа, если вас беспокоит количество генерируемых ходов, когда вы берете CreditCardзначение по значению, вы можете определить две перегрузки конструктора, одна из которых принимает ссылку lvalue на const(CreditCard const& ), а другая - ссылка rvalue ( CreditCard&&).
Разрешение перегрузки выберет первое при передаче lvalue (в этом случае будет выполнено одно копирование) и второе при передаче rvalue (в этом случае будет выполнено одно перемещение).
Account(std::string number, float amount, CreditCard const& creditCard)
: number(number), amount(amount), creditCard(creditCard) // copy here
{ }
Account(std::string number, float amount, CreditCard&& creditCard)
: number(number), amount(amount), creditCard(std::move(creditCard)) // move here
{ }
Ваше использование std::forward<>обычно наблюдается, когда вы хотите добиться идеальной пересылки . В этом случае ваш конструктор будет фактически шаблоном конструктора и будет выглядеть примерно так:
template<typename C>
Account(std::string number, float amount, C&& creditCard)
: number(number), amount(amount), creditCard(std::forward<C>(creditCard)) { }
В некотором смысле, это объединяет обе перегрузки, которые я показал ранее, в одну единственную функцию: Cбудет выведено, CreditCard&что это произойдет в случае, если вы передаете lvalue, и из-за правил свертывания ссылок это приведет к созданию этой функции:
Account(std::string number, float amount, CreditCard& creditCard) :
number(num), amount(amount), creditCard(std::forward<CreditCard&>(creditCard))
{ }
Это приведет к копии-конструкцию из creditCard, как вы хотите. С другой стороны, когда будет передано rvalue, Cбудет выведено, что есть CreditCard, и вместо этого будет создан экземпляр этой функции:
Account(std::string number, float amount, CreditCard&& creditCard) :
number(num), amount(amount), creditCard(std::forward<CreditCard>(creditCard))
{ }
Это вызовет перемещение-конструкцию из creditCard, которая является то , что вы хотите (потому что значение передается является Rvalue, и это означает , что мы имеем право двигаться от него).