यह समझने के लिए कि यह एक अच्छा पैटर्न क्यों है, हमें C ++ 03 और C ++ 11 दोनों में विकल्पों की जांच करनी चाहिए।
हमारे पास लेने की C ++ 03 विधि है std::string const&
:
struct S
{
std::string data;
S(std::string const& str) : data(str)
{}
};
इस मामले में, हमेशा एक ही प्रति प्रदर्शित की जाएगी। यदि आप कच्चे सी स्ट्रिंग std::string
से निर्माण करते हैं, तो एक निर्माण किया जाएगा, फिर दोबारा कॉपी किया जाएगा: दो आवंटन।
एक संदर्भ लेने के लिए C ++ 03 विधि है std::string
, फिर इसे स्थानीय में स्वैप करना है std::string
:
struct S
{
std::string data;
S(std::string& str)
{
std::swap(data, str);
}
};
"चाल शब्दार्थ" का C ++ 03 संस्करण है, और swap
अक्सर ऐसा करने के लिए बहुत सस्ता होने के लिए अनुकूलित किया जा सकता है (बहुत पसंद है move
)। इसका संदर्भ में विश्लेषण भी किया जाना चाहिए:
S tmp("foo"); // illegal
std::string s("foo");
S tmp2(s); // legal
और आपको एक गैर-अस्थायी बनाने के लिए मजबूर करता है std::string
, फिर इसे त्याग दें। (एक अस्थायी std::string
एक गैर-कास्ट संदर्भ के लिए बाध्य नहीं कर सकते हैं)। हालाँकि, केवल एक ही आवंटन किया जाता है। C ++ 11 संस्करण को एक की &&
आवश्यकता होगी और आपको इसे std::move
अस्थायी रूप से या इसके साथ कॉल करने की आवश्यकता होगी : इसके लिए कॉल करने वाले को स्पष्ट रूप से कॉल के बाहर एक कॉपी बनाता है, और उस कॉपी को फ़ंक्शन या कंस्ट्रक्टर में स्थानांतरित करना होगा।
struct S
{
std::string data;
S(std::string&& str): data(std::move(str))
{}
};
उपयोग:
S tmp("foo"); // legal
std::string s("foo");
S tmp2(std::move(s)); // legal
अगला, हम पूर्ण C ++ 11 संस्करण कर सकते हैं, जो प्रतिलिपि और move
:
struct S
{
std::string data;
S(std::string const& str) : data(str) {} // lvalue const, copy
S(std::string && str) : data(std::move(str)) {} // rvalue, move
};
हम तब जांच कर सकते हैं कि इसका उपयोग कैसे किया जाता है:
S tmp( "foo" ); // a temporary `std::string` is created, then moved into tmp.data
std::string bar("bar"); // bar is created
S tmp2( bar ); // bar is copied into tmp.data
std::string bar2("bar2"); // bar2 is created
S tmp3( std::move(bar2) ); // bar2 is moved into tmp.data
यह बहुत स्पष्ट है कि यह 2 अधिभार तकनीक कम से कम कुशल है, यदि ऐसा नहीं है, तो उपरोक्त दो सी ++ 03 शैलियों की तुलना में। मैं इस 2-अधिभार संस्करण को "सबसे इष्टतम" संस्करण दूंगा।
अब, हम टेक-बाय-कॉपी संस्करण की जाँच करेंगे:
struct S2 {
std::string data;
S2( std::string arg ):data(std::move(x)) {}
};
उन परिदृश्यों में से प्रत्येक में:
S2 tmp( "foo" ); // a temporary `std::string` is created, moved into arg, then moved into S2::data
std::string bar("bar"); // bar is created
S2 tmp2( bar ); // bar is copied into arg, then moved into S2::data
std::string bar2("bar2"); // bar2 is created
S2 tmp3( std::move(bar2) ); // bar2 is moved into arg, then moved into S2::data
यदि आप इस साइड-बाय-साइड की "सबसे इष्टतम" संस्करण के साथ तुलना करते हैं, तो हम बिल्कुल एक अतिरिक्त करते हैं move
! एक बार नहीं हम एक अतिरिक्त करते हैं copy
।
इसलिए अगर हम मानते हैं कि move
यह सस्ता है, तो यह संस्करण हमें लगभग सबसे इष्टतम संस्करण के समान प्रदर्शन देता है, लेकिन 2 गुना कम कोड।
और यदि आप 2 से 10 तर्क कह रहे हैं, तो कोड में कमी घातीय है - 1 तर्क के साथ 2x गुणा कम, 2 के साथ 4x, 3 के साथ 8x, 4 तर्क के साथ 16x।
अब, हम इसे सही फॉरवर्डिंग और SFINAE के माध्यम से प्राप्त कर सकते हैं, जो आपको एक एकल निर्माणकर्ता या फ़ंक्शन टेम्प्लेट लिखने की अनुमति देता है जो 10 तर्क लेता है, यह सुनिश्चित करने के लिए SFINAE करता है कि तर्क उपयुक्त प्रकार के हैं, और फिर उन्हें स्थानांतरित या कॉपी करता है। आवश्यकतानुसार स्थानीय राज्य। हालांकि यह कार्यक्रम आकार समस्या में हजार गुना वृद्धि को रोकता है, फिर भी इस टेम्पलेट से उत्पन्न कार्यों का एक पूरा ढेर हो सकता है। (टेंपलेट फंक्शन झटपट कार्य उत्पन्न करते हैं)
और बहुत सारे उत्पन्न कार्यों का अर्थ बड़ा निष्पादन योग्य कोड आकार है, जो स्वयं प्रदर्शन को कम कर सकता है।
कुछ move
एस की लागत के लिए , हमें कम कोड और लगभग समान प्रदर्शन मिलता है, और अक्सर कोड को समझना आसान होता है।
अब, यह केवल इसलिए काम करता है क्योंकि हम जानते हैं, जब फ़ंक्शन (इस मामले में, एक निर्माता) कहा जाता है, कि हम उस तर्क की एक स्थानीय प्रतिलिपि चाहते हैं। विचार यह है कि यदि हम जानते हैं कि हम एक प्रतिलिपि बनाने जा रहे हैं, तो हमें कॉल करने वाले को यह बताना चाहिए कि हम इसे अपनी तर्क सूची में डालकर एक प्रति बना रहे हैं। वे तब इस तथ्य के आसपास अनुकूलन कर सकते हैं कि वे हमें एक प्रति (उदाहरण के लिए, हमारे तर्क में स्थानांतरित करके) देने जा रहे हैं।
'टेक बाय वैल्यू' तकनीक का एक और फायदा यह है कि अक्सर मूव करने वाले कंस्ट्रक्टर्स नोएसेप्ट होते हैं। इसका मतलब है कि जो काम बाय-वैल्यू लेते हैं और अपने तर्क से बाहर निकलते हैं, वे अक्सर नॉइसेप्ट हो सकते हैं, जो किसी भी throw
एस को अपने शरीर से बाहर निकालकर कॉलिंग स्कोप में ले जाते हैं। (जो कभी-कभी प्रत्यक्ष निर्माण के माध्यम से इसे टाल सकते हैं, या वस्तुओं का निर्माण कर सकते हैं और move
तर्क में, यह नियंत्रित कर सकते हैं कि फेंकना कहां होता है)। तरीकों को बनाना अक्सर इसके लायक होता है।