सबसे महत्वपूर्ण सवाल FIRST:
क्या सी ++ में पैरामीटर भेजने का कोई सर्वोत्तम तरीका है क्योंकि मैं वास्तव में इसे ढूंढता हूं, आइए हम कहते हैं, तुच्छ नहीं
यदि आपके फ़ंक्शन को पास की जा रही मूल वस्तु को संशोधित करने की आवश्यकता है , ताकि कॉल रिटर्न के बाद, कॉल करने वाले को उस वस्तु में संशोधन दिखाई दे, तो आपको लैवल्यू संदर्भ से गुजरना चाहिए :
void foo(my_class& obj)
{
// Modify obj here...
}
यदि आपके फ़ंक्शन को मूल ऑब्जेक्ट को संशोधित करने की आवश्यकता नहीं है, और इसकी एक प्रति बनाने की आवश्यकता नहीं है (दूसरे शब्दों में, इसे केवल इसकी स्थिति का निरीक्षण करने की आवश्यकता है), तो आपको lvalue संदर्भ सेconst गुजरना चाहिए :
void foo(my_class const& obj)
{
// Observe obj here
}
यह आपको lvalues साथ समारोह दोनों फोन करने की अनुमति देगा और rvalues साथ (lvalues एक स्थिर पहचान के साथ वस्तुओं रहे हैं) (rvalues उदाहरण के लिए, कर रहे हैं temporaries , या वस्तुओं को आप कॉल के परिणाम के रूप से स्थानांतरित करने के बारे में कर रहे हैं 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...
}
उपरोक्त फ़ंक्शन को लागू करने पर परिणाम हमेशा एक प्रतिलिपि में होगा, जब लवलीनों को पास करना होगा, और एक चाल में जब प्रतिद्वंद्वियों को पास करना होगा। यदि आपके फ़ंक्शन को इस ऑब्जेक्ट को कहीं स्टोर करने की आवश्यकता है, तो आप इससे एक अतिरिक्त कदम उठा सकते हैं (उदाहरण के लिए, मामले foo()में एक सदस्य फ़ंक्शन है जिसे डेटा सदस्य में मान संग्रहीत करने की आवश्यकता है )।
मामले में चालें प्रकार की वस्तुओं के लिए महंगी होती हैंmy_class , तो आप ओवरलोडिंग पर विचार कर सकते हैं foo()और एक संस्करण प्रदान कर सकते हैं (lvalue संदर्भ को स्वीकार करते हुए const) और एक संस्करण rvalues के लिए (एक संदर्भ को स्वीकार करते हुए):
// 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!
}
यह भी ध्यान दें, कि आप सामान्य रूप से टेम्पलेट-आधारित समाधान का सहारा नहीं लेते हैं जब तक कि आपके पास इसका कोई अच्छा कारण न हो, क्योंकि यह आपके कोड को पढ़ने के लिए कठिन बना देता है। आम तौर पर, आपको स्पष्टता और सरलता पर ध्यान देना चाहिए ।
उपरोक्त केवल सरल दिशानिर्देश हैं, लेकिन अधिकांश समय वे आपको अच्छे डिजाइन निर्णयों की ओर इंगित करेंगे।
आपके पोस्ट के संबंध का पता लगाना:
अगर मैं इसे […] के रूप में फिर से लिखूंगा तो 2 चालें होंगी और कोई कॉपी नहीं होगी।
यह सही नहीं है। इसके साथ शुरू करने के लिए, एक रेवल्यू रेफरेंस एक लैवल्यू से बंध नहीं सकता है, इसलिए यह केवल तभी संकलित होगा जब आप 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एक लवलीन है और रेवले के सन्दर्भ लवलीन से बंध नहीं सकते। इसके अलावा, जब किसी ऑब्जेक्ट के संदर्भ को बाइंड किया जाता है, तो कोई भी चालन नहीं किया जाता है : यह केवल एक संदर्भ बाइंडिंग है। इस प्रकार, केवल एक चाल होगी।
इसलिए इस उत्तर के पहले भाग में दिए गए दिशा-निर्देशों के आधार पर, यदि आप एक CreditCardमूल्य लेते समय उत्पन्न होने वाली संख्या की संख्या से चिंतित हैं, तो आप दो कंस्ट्रक्टर अधिभार को परिभाषित कर सकते हैं, एक लेवल्यू संदर्भ ले रहा है const( CreditCard const&) और एक ले रहा है एक संदर्भ संदर्भ ( CreditCard&&)।
ओवरलोड रिज़ॉल्यूशन एक लैवल्यू पास करते समय पूर्व का चयन करेगा (इस मामले में, एक प्रति का प्रदर्शन किया जाएगा) और बाद में एक रिवेल्यू पास करते समय (इस मामले में, एक चाल का प्रदर्शन किया जाएगा)।
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 से गुजर रहे हैं, और संदर्भ नियम टूट के कारण, यह इस समारोह instantiated हो जाएंगी:
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, जो आप चाहते हैं (क्योंकि पारित किया जा रहा मूल्य एक प्रतिद्वंद्विता है, और इसका मतलब है कि हम इसे से स्थानांतरित करने के लिए अधिकृत हैं)।