PERTANYAAN PALING PENTING PERTAMA:
Apakah ada praktik terbaik tentang cara mengirim parameter dalam C ++ karena saya benar-benar menemukannya, katakanlah, tidak sepele
Jika fungsi Anda perlu mengubah objek asli yang sedang diteruskan, sehingga setelah panggilan kembali, modifikasi pada objek itu akan terlihat oleh pemanggil, Anda harus meneruskan referensi lvalue :
void foo(my_class& obj)
{
// Modify obj here...
}
Jika fungsi Anda tidak perlu memodifikasi objek aslinya, dan tidak perlu membuat salinannya (dengan kata lain, hanya perlu mengamati statusnya), maka Anda harus meneruskan referensi lvalue keconst :
void foo(my_class const& obj)
{
// Observe obj here
}
Ini akan memungkinkan Anda untuk memanggil fungsi baik dengan lvalues (lvalues adalah objek dengan identitas stabil) dan dengan rvalues (rvalues adalah, misalnya temporaries , atau objek yang akan Anda pindahkan sebagai hasil pemanggilan std::move()).
Seseorang juga dapat berargumen bahwa untuk tipe atau tipe fundamental yang penyalinannya cepat , sepertiint , bool, atau char, tidak perlu lewat referensi jika fungsi hanya perlu mengamati nilai, dan lewat nilai harus disukai . Itu benar jika semantik referensi tidak diperlukan, tetapi bagaimana jika fungsi ingin menyimpan pointer ke objek input yang sama di suatu tempat, sehingga pembacaan di masa depan melalui pointer itu akan melihat modifikasi nilai yang telah dilakukan di beberapa bagian lain dari kode? Dalam hal ini, melewatkan referensi adalah solusi yang tepat.
Jika fungsi Anda tidak perlu mengubah objek asli, tetapi perlu menyimpan salinan objek itu ( mungkin untuk mengembalikan hasil transformasi input tanpa mengubah input ), maka Anda dapat mempertimbangkan untuk mengambil berdasarkan nilai :
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...
}
Memanggil fungsi di atas akan selalu menghasilkan satu salinan saat meneruskan nilai l, dan satu salinan saat meneruskan nilai r. Jika fungsi Anda perlu untuk menyimpan objek di suatu tempat ini, Anda bisa melakukan tambahan langkah darinya (misalnya, dalam kasus foo()ini adalah fungsi anggota yang perlu menyimpan nilai dalam anggota data ).
Dalam kasus bergerak mahal untuk objek jenismy_class , maka Anda dapat mempertimbangkan untuk membebani foo()dan memberikan satu versi untuk lvalues (menerima referensi lvalue const) dan satu versi untuk rvalues (menerima referensi 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...
}
Fungsi di atas sangat mirip, pada kenyataannya, Anda bisa membuat satu fungsi darinya: foo()bisa menjadi template fungsi dan Anda bisa menggunakan penerusan sempurna untuk menentukan apakah perpindahan atau salinan objek yang diteruskan akan dihasilkan secara internal:
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...
}
Anda mungkin ingin mempelajari lebih lanjut tentang desain ini dengan menonton ceramah oleh Scott Meyers ini (ingatlah fakta bahwa istilah " Referensi Universal " yang dia gunakan tidak standar).
Satu hal yang perlu diingat adalah bahwa std::forwardbiasanya akan berakhir di a perpindahan nilai r, jadi meskipun terlihat relatif tidak berbahaya, meneruskan objek yang sama beberapa kali dapat menjadi sumber masalah - misalnya, berpindah dari objek yang sama dua kali! Jadi berhati-hatilah untuk tidak mengulanginya, dan jangan meneruskan argumen yang sama beberapa kali dalam pemanggilan fungsi:
template<typename C>
void foo(C&& obj)
{
bar(std::forward<C>(obj), std::forward<C>(obj)); // Dangerous!
}
Perhatikan juga, bahwa Anda biasanya tidak menggunakan solusi berbasis template kecuali Anda memiliki alasan kuat untuk itu, karena membuat kode Anda lebih sulit untuk dibaca. Biasanya, Anda harus fokus pada kejelasan dan kesederhanaan .
Di atas hanyalah pedoman sederhana, tetapi sebagian besar waktu itu akan mengarahkan Anda ke keputusan desain yang baik.
TENTANG SISA POSTING ANDA:
Jika saya menulis ulang sebagai [...] akan ada 2 gerakan dan tidak ada salinan.
Ini tidak benar. Untuk memulainya, referensi rvalue tidak bisa mengikat ke lvalue, jadi ini hanya akan dikompilasi ketika Anda meneruskan rvalue type CreditCardke konstruktor Anda. Misalnya:
// 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));
Tetapi itu tidak akan berhasil jika Anda mencoba melakukan ini:
CreditCard cc("12345",2,2015,1001);
Account acc("asdasd",345, cc); // ERROR! cc is an lvalue
Karena ccadalah nilai l dan referensi nilai r tidak dapat mengikat ke nilai l. Selain itu, saat mengikat referensi ke suatu objek, tidak ada pemindahan yang dilakukan : itu hanya mengikat referensi. Jadi, hanya akan ada satu gerakan.
Jadi berdasarkan pedoman yang diberikan di bagian pertama jawaban ini, jika Anda khawatir dengan jumlah gerakan yang dihasilkan saat Anda mengambil CreditCardnilai by, Anda dapat menentukan dua overload konstruktor, satu mengambil referensi lvalue ke const( CreditCard const&) dan satu pengambilan referensi rvalue ( CreditCard&&).
Resolusi kelebihan beban akan memilih yang pertama saat meneruskan nilai l (dalam hal ini, satu salinan akan dilakukan) dan yang terakhir saat meneruskan nilai r (dalam hal ini, satu gerakan akan dilakukan).
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
{ }
Penggunaan Anda std::forward<>biasanya terlihat saat Anda ingin mencapai penerusan sempurna . Dalam hal ini, konstruktor Anda sebenarnya adalah template konstruktor , dan akan terlihat kurang lebih seperti berikut
template<typename C>
Account(std::string number, float amount, C&& creditCard)
: number(number), amount(amount), creditCard(std::forward<C>(creditCard)) { }
Dalam arti tertentu, ini menggabungkan kedua overload yang telah saya tunjukkan sebelumnya menjadi satu fungsi tunggal: Cakan disimpulkan CreditCard&jika Anda meneruskan nilai l, dan karena referensi aturan menciutkan, ini akan menyebabkan fungsi ini dibuat:
Account(std::string number, float amount, CreditCard& creditCard) :
number(num), amount(amount), creditCard(std::forward<CreditCard&>(creditCard))
{ }
Hal ini akan menyebabkan copy-konstruksi dari creditCard, seperti yang Anda inginkan. Di sisi lain, ketika nilai r dilewatkan, Cakan disimpulkan menjadi CreditCard, dan fungsi ini akan dipakai sebagai gantinya:
Account(std::string number, float amount, CreditCard&& creditCard) :
number(num), amount(amount), creditCard(std::forward<CreditCard>(creditCard))
{ }
Hal ini akan menyebabkan langkah-konstruksi dari creditCard, yang adalah apa yang Anda inginkan (karena nilai yang berlalu adalah nilai p, dan itu berarti kami berwenang untuk berpindah dari itu).