НАЙБІЛЬШЕ ПИТАННЯ ПИТАННЯ:
Чи є найкращі практики, як надсилати параметри в C ++, тому що я дійсно вважаю це, скажімо, нетривіальним
Якщо вашій функції потрібно змінити оригінальний об'єкт, що передається, щоб після повернення дзвінка модифікація цього об’єкта буде видно абоненту, то вам слід пройти через посилання lvalue :
void foo(my_class& obj)
{
// Modify obj here...
}
Якщо вашій функції не потрібно змінювати оригінальний об'єкт і не потрібно створювати його копію (іншими словами, йому потрібно лише спостерігати за його станом), тоді вам слід пройти через посилання lvalue наconst
:
void foo(my_class const& obj)
{
// Observe obj here
}
Це дозволить вам викликати функцію як з lvalues (lvalues - це об'єкти зі стійкою ідентичністю), так і з rvalues (rvalues - це, наприклад, тимчасові або об'єкти, з яких ви збираєтеся перейти в результаті виклику 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...
}
Викликання вищевказаної функції завжди призведе до отримання однієї копії при передачі значень, а в одному - при переміщенні значень rvalues. Якщо вашій функції потрібно десь зберігати цей об’єкт, ви можете виконати додатковий переміщення від нього (наприклад, у випадку foo()
- це функція-член, якій потрібно зберігати значення в члені даних ).
Якщо переміщення дорого коштують для об'єктів типу my_class
, то ви можете розглянути можливість перевантаження foo()
та надати одну версію для lvalues (прийняття посилання на значення lvalue const
) та одну версію для rvalues (прийняття посилання на 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
, як правило, в результаті руху оцінюються, тому, хоча це виглядає відносно невинне, пересилання одного і того ж об'єкта кілька разів може бути причиною неприємностей - наприклад, переміщення з одного і того ж об’єкта двічі! Тому будьте обережні, щоб не ставити це в цикл і не пересилати один і той же аргумент кілька разів у виклик функції:
template<typename C>
void foo(C&& obj)
{
bar(std::forward<C>(obj), std::forward<C>(obj)); // Dangerous!
}
Також зауважте, що зазвичай ви не вдаєтеся до рішення на основі шаблонів, якщо у вас немає вагомих причин, оскільки це робить ваш код важчим для читання. Зазвичай вам слід зосередитись на чіткості та простоті .
Вищенаведені - це просто прості вказівки, але більшу частину часу вони спрямовуватимуть вас на гарні дизайнерські рішення.
Щодо решти власного пошти:
Якщо я перепишу його як [...], буде два ходи, а копія не буде.
Це неправильно. Для початку посилання на 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 не може пов'язуватися з lvalues. Більше того, при прив’язуванні посилання на об'єкт не рухається : це просто прив'язка посилання. Таким чином, буде лише один хід.
Отже, виходячи з вказівок, наведених у першій частині цієї відповіді, якщо ви переймаєтесь кількістю ходів, що створюються, коли ви приймаєте CreditCard
значення, ви можете визначити два перевантаження конструктора, один з яких посилається на значення " const
(" CreditCard const&
) і "один" посилання на оцінку ( 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&
випадок, якщо ви передаєте значення, і завдяки правилам згортання посилань це призведе до активізації цієї функції:
Account(std::string number, float amount, CreditCard& creditCard) :
number(num), amount(amount), creditCard(std::forward<CreditCard&>(creditCard))
{ }
Це призведе до копії-конструкцію з creditCard
, як ви хочете. З іншого боку, коли буде передано оцінку, C
буде виведено CreditCard
, що ця функція буде замість цього екземпляром:
Account(std::string number, float amount, CreditCard&& creditCard) :
number(num), amount(amount), creditCard(std::forward<CreditCard>(creditCard))
{ }
Це викличе переміщення-конструкцію з creditCard
, яка є те , що ви хочете (тому що значення передається є Rvalue, і це означає , що ми маємо право рухатися від нього).