ÖNCE EN ÖNEMLİ SORU:
C ++ 'da parametrelerin nasıl gönderileceğine dair en iyi uygulamalar var mı, çünkü onu gerçekten buluyorum, diyelim, önemsiz değil
İşlevinizin , iletilen orijinal nesneyi değiştirmesi gerekiyorsa , çağrı geri döndükten sonra, o nesnede yapılan değişiklikler arayan tarafından görülebilir, o zaman lvalue referansına geçmelisiniz :
void foo(my_class& obj)
{
// Modify obj here...
}
İşlevinizin orijinal nesneyi değiştirmesi gerekmiyorsa ve bunun bir kopyasını oluşturması gerekmiyorsa (başka bir deyişle, yalnızca durumunu gözlemlemesi gerekir), o zaman şunlara lvalue referansıylaconst
geçmelisiniz :
void foo(my_class const& obj)
{
// Observe obj here
}
Bu SolDeğerler ile işlevini hem çağırmak sağlayacak ve SağDeğerler ile (SağDeğerler örneği için vardır (Sol taraf istikrarlı kimliğiyle nesnelerdir) geçicilere veya aradığınız sonucu olarak hareket üzereyiz nesneleri std::move()
).
Bir de iddia olabilir kopyalama hızlı olduğu için temel tip veya türlerine gibi int
, bool
ya da char
, fonksiyon sadece değeri gözlemlemek için gereken ve eğer referans olarak geçmesine gerek yoktur değeri geçen tercih edilmelidir . Bu, referans semantiğine ihtiyaç duyulmuyorsa doğrudur , ama ya işlev bir yerde aynı girdi nesnesine bir işaretçi depolamak isterse, böylece gelecekte bu işaretçi aracılığıyla okuma, öğenin başka bir bölümünde gerçekleştirilen değer değişikliklerini görecektir. kod? Bu durumda, referansla geçmek doğru çözümdür.
İşlevinizin orijinal nesneyi değiştirmesi gerekmiyorsa, ancak bu nesnenin bir kopyasını saklaması gerekiyorsa ( muhtemelen girdiyi değiştirmeden girdinin dönüştürülmesinin sonucunu döndürmek için ), o zaman değer olarak almayı düşünebilirsiniz :
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...
}
Yukarıdaki işlevi çağırmak, her zaman ldeğerleri geçerken bir kopya ve r değerleri geçerken bir hamle ile sonuçlanacaktır. Fonksiyonun bu nesne bir yere saklamak gerekiyorsa, ek bir gerçekleştirebilir hareket ondan (örneğin durumda foo()
olduğunu ihtiyaçlar veri üyesi değeri saklamak için bir üye fonksiyonu ).
Türdeki nesneler için hareketlerin pahalı olması durumundamy_class
, aşırı yüklemeyi düşünebilir foo()
ve ldeğerler için bir sürüm (bir ldeğer başvurusunu kabul ederek const
) ve r değerleri için bir sürüm (bir r değeri başvurusunu kabul ederek) sağlayabilirsiniz:
// 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...
}
Yukarıdaki işlevler o kadar benzer ki, ondan tek bir işlev yapabilirsiniz: foo()
bir işlev şablonu olabilir ve geçirilen nesnenin bir hareketinin veya bir kopyasının dahili olarak oluşturulup oluşturulmayacağını belirlemek için mükemmel iletmeyi kullanabilirsiniz :
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...
}
Scott Meyers'in bu konuşmasını izleyerek bu tasarım hakkında daha fazla bilgi edinmek isteyebilirsiniz (kullandığı " Evrensel Referanslar " teriminin standart dışı olduğunu unutmayın).
Akılda tutulması gereken bir şey, bunun std::forward
genellikle rdeğerler için bir hareketle sonuçlanacağıdır , bu nedenle nispeten masum görünse de, aynı nesneyi birden çok kez iletmek bir sorun kaynağı olabilir - örneğin, aynı nesneden iki kez hareket etmek! Bu nedenle, bunu bir döngüye koymamaya ve aynı argümanı bir işlev çağrısında birden çok kez iletmemeye dikkat edin:
template<typename C>
void foo(C&& obj)
{
bar(std::forward<C>(obj), std::forward<C>(obj)); // Dangerous!
}
Ayrıca, iyi bir nedeniniz olmadıkça normalde şablon tabanlı çözüme başvurmadığınıza dikkat edin, çünkü bu kodunuzun okunmasını zorlaştırır. Normalde açıklığa ve basitliğe odaklanmalısınız .
Yukarıdakiler sadece basit yönergelerdir, ancak çoğu zaman sizi iyi tasarım kararlarına yönlendireceklerdir.
YAYININIZIN KALANLARI İLE İLGİLİ
[...] olarak yeniden yazarsam 2 hareket olur ve kopyası olmaz.
Bu doğru değil. Başlangıç olarak, bir rvalue başvurusu bir lvalue'ya bağlanamaz, bu nedenle bu yalnızca kurucunuza bir rvalue türü geçirdiğinizde derlenir CreditCard
. Örneğin:
// 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));
Ama bunu yapmaya çalışırsan işe yaramayacak:
CreditCard cc("12345",2,2015,1001);
Account acc("asdasd",345, cc); // ERROR! cc is an lvalue
Çünkü cc
bir lvalue ve rvalue referansları ldeğerlere bağlanamaz. Dahası, bir nesneye bir referans bağlarken hiçbir hareket gerçekleştirilmez : bu sadece bir referans bağlamadır. Böylece, sadece bir hareket olacaktır.
Bu nedenle, bu cevabın ilk bölümünde verilen yönergelere göre, bir CreditCard
değer olarak aldığınızda üretilen hareket sayısıyla ilgileniyorsanız , biri const
( CreditCard const&
) ' ye bir ldeğer referansı ve diğeri alarak iki kurucu aşırı yüklemesi tanımlayabilirsiniz. bir rvalue referansı ( CreditCard&&
).
Aşırı yük çözünürlüğü, bir ldeğer geçerken ilkini seçer (bu durumda, bir kopya gerçekleştirilir) ve ikincisi, bir r değeri geçerken (bu durumda, bir hareket gerçekleştirilir).
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
{ }
Kullanımınız std::forward<>
, normalde mükemmel bir yönlendirme elde etmek istediğinizde görülür . Bu durumda, kurucunuz aslında bir yapıcı şablon olur ve aşağı yukarı aşağıdaki gibi görünür
template<typename C>
Account(std::string number, float amount, C&& creditCard)
: number(number), amount(amount), creditCard(std::forward<C>(creditCard)) { }
Bir anlamda, bu, daha önce gösterdiğim her iki aşırı yüklemeyi tek bir işlevde birleştirir: bir l C
değeri CreditCard&
geçmeniz durumunda olduğu çıkarılır ve referans çökme kuralları nedeniyle, bu işlevin somutlaştırılmasına neden olur:
Account(std::string number, float amount, CreditCard& creditCard) :
number(num), amount(amount), creditCard(std::forward<CreditCard&>(creditCard))
{ }
Bu neden olacaktır kopyalamaya karşı inşaat ve creditCard
istediğiniz gibi. Öte yandan, bir rvalue iletildiğinde, C
olduğu çıkarılır CreditCard
ve bunun yerine bu işlev başlatılır:
Account(std::string number, float amount, CreditCard&& creditCard) :
number(num), amount(amount), creditCard(std::forward<CreditCard>(creditCard))
{ }
Bu neden olacaktır hareket-inşaat ve creditCard
(geçirilen değer bir rvalue olduğunu ve araçlarının biz ondan hareket etmek yetkisine sahiptir çünkü) ne istediğinizi olan.