मेरा पहला उत्तर शब्दार्थ को स्थानांतरित करने के लिए एक अत्यंत सरलीकृत परिचय था, और इसे सरल रखने के उद्देश्य से कई विवरण छोड़ दिए गए थे। हालांकि, शब्दार्थ को स्थानांतरित करने के लिए बहुत कुछ है, और मुझे लगा कि अंतराल को भरने के लिए एक दूसरे उत्तर के लिए समय था। पहला उत्तर पहले से ही काफी पुराना है, और इसे पूरी तरह से अलग पाठ के साथ बदलने का अधिकार नहीं था। मुझे लगता है कि यह अभी भी पहले परिचय के रूप में अच्छी तरह से कार्य करता है। लेकिन अगर आप गहरी खुदाई करना चाहते हैं, तो पढ़ें :)
स्टीफन टी। लावेज ने बहुमूल्य प्रतिक्रिया प्रदान करने के लिए समय लिया। बहुत बहुत धन्यवाद, स्टेफ़न!
परिचय
मूवमेंट शब्दार्थ एक वस्तु को कुछ शर्तों के तहत, किसी अन्य वस्तु के बाहरी संसाधनों का स्वामित्व लेने की अनुमति देता है। यह दो तरह से महत्वपूर्ण है:
महंगी प्रतियों को सस्ते चालों में बदलना। एक उदाहरण के लिए मेरा पहला उत्तर देखें। ध्यान दें कि यदि कोई वस्तु कम से कम एक बाहरी संसाधन (या तो प्रत्यक्ष या अप्रत्यक्ष रूप से अपने सदस्य वस्तुओं के माध्यम से) का प्रबंधन नहीं करती है, तो स्थानांतरित शब्दार्थ कॉपी प्रति शब्द विज्ञान पर कोई लाभ नहीं देगा। उस स्थिति में, किसी वस्तु की नकल करना और किसी वस्तु को ले जाने का मतलब ठीक उसी चीज से है:
class cannot_benefit_from_move_semantics
{
int a; // moving an int means copying an int
float b; // moving a float means copying a float
double c; // moving a double means copying a double
char d[64]; // moving a char array means copying a char array
// ...
};
सुरक्षित "चाल-केवल" प्रकारों को लागू करना; यह है, प्रकार जिसके लिए नकल मतलब नहीं है, लेकिन चलती है। उदाहरणों में अद्वितीय स्वामित्व वाले शब्दार्थ के साथ ताले, फ़ाइल हैंडल और स्मार्ट पॉइंटर्स शामिल हैं। नोट: यह उत्तर चर्चा करता है std::auto_ptr
, एक उपेक्षित C ++ 98 मानक पुस्तकालय टेम्पलेट, जिसे std::unique_ptr
C ++ 11 द्वारा प्रतिस्थापित किया गया था । इंटरमीडिएट सी ++ प्रोग्रामर शायद कम से कम कुछ हद तक परिचित हैं std::auto_ptr
, और "चालित अर्थ विज्ञान" के कारण यह प्रदर्शित करता है, यह सी ++ 11 में चाल शब्दार्थों पर चर्चा करने के लिए एक अच्छी शुरुआत की तरह लगता है। YMMV।
एक चाल क्या है?
C ++ 98 मानक पुस्तकालय एक स्मार्ट पॉइंटर प्रदान करता है जिसे अद्वितीय स्वामित्व शब्दार्थ कहा जाता है std::auto_ptr<T>
। यदि आप इससे अपरिचित हैं auto_ptr
, तो इसका उद्देश्य यह सुनिश्चित करना है कि एक गतिशील रूप से आवंटित वस्तु हमेशा जारी होती है, यहां तक कि अपवादों के सामने भी:
{
std::auto_ptr<Shape> a(new Triangle);
// ...
// arbitrary code, could throw exceptions
// ...
} // <--- when a goes out of scope, the triangle is deleted automatically
auto_ptr
इसके "नकल" व्यवहार के बारे में असामान्य बात है:
auto_ptr<Shape> a(new Triangle);
+---------------+
| triangle data |
+---------------+
^
|
|
|
+-----|---+
| +-|-+ |
a | p | | | |
| +---+ |
+---------+
auto_ptr<Shape> b(a);
+---------------+
| triangle data |
+---------------+
^
|
+----------------------+
|
+---------+ +-----|---+
| +---+ | | +-|-+ |
a | p | | | b | p | | | |
| +---+ | | +---+ |
+---------+ +---------+
ध्यान दें कि कैसे के b
साथ आरंभ त्रिकोण को कॉपी नहींa
करता है , बल्कि इसके बजाय से त्रिकोण के स्वामित्व को स्थानांतरित करता है । हम यह भी कह " है में चले गए या" त्रिकोण है " ले जाया गया से करने के लिए "। यह भ्रामक लग सकता है क्योंकि त्रिकोण हमेशा स्मृति में एक ही स्थान पर रहता है।a
b
a
b
a
b
किसी ऑब्जेक्ट को स्थानांतरित करने का अर्थ है कुछ संसाधन के स्वामित्व को किसी अन्य ऑब्जेक्ट को प्रबंधित करना।
कॉपी कंस्ट्रक्टर auto_ptr
शायद कुछ इस तरह दिखता है (कुछ सरलीकृत):
auto_ptr(auto_ptr& source) // note the missing const
{
p = source.p;
source.p = 0; // now the source no longer owns the object
}
खतरनाक और हानिरहित चाल
इसके बारे auto_ptr
में खतरनाक बात यह है कि प्रतिलिपि की तरह जो वाक्यात्मक रूप से दिखता है वह वास्तव में एक चाल है। किसी स्थानांतरित फ़ंक्शन से सदस्य फ़ंक्शन को कॉल करने का प्रयास auto_ptr
अपरिभाषित व्यवहार को लागू करेगा, इसलिए आपको auto_ptr
इसे स्थानांतरित करने के बाद उपयोग करने के लिए बहुत सावधान रहना होगा :
auto_ptr<Shape> a(new Triangle); // create triangle
auto_ptr<Shape> b(a); // move a into b
double area = a->area(); // undefined behavior
लेकिन हमेशा खतरनाक auto_ptr
नहीं होता है। कारखाने के कार्यों के लिए एक पूरी तरह से ठीक उपयोग के मामले हैं auto_ptr
:
auto_ptr<Shape> make_triangle()
{
return auto_ptr<Shape>(new Triangle);
}
auto_ptr<Shape> c(make_triangle()); // move temporary into c
double area = make_triangle()->area(); // perfectly safe
ध्यान दें कि दोनों उदाहरण एक ही वाक्यविन्यास पैटर्न का अनुसरण कैसे करते हैं:
auto_ptr<Shape> variable(expression);
double area = expression->area();
और फिर भी, उनमें से एक अपरिभाषित व्यवहार करता है, जबकि दूसरा नहीं करता है। तो भावों a
और के बीच अंतर क्या है make_triangle()
? क्या वे दोनों एक ही प्रकार के नहीं हैं? वास्तव में वे हैं, लेकिन उनकी अलग-अलग मूल्य श्रेणियां हैं ।
मूल्य श्रेणियों
जाहिर है, अभिव्यक्ति के बीच कुछ गहरा अंतर होना चाहिए a
जो एक auto_ptr
चर को दर्शाता है, और अभिव्यक्ति make_triangle()
जो एक फ़ंक्शन के कॉल को निरूपित करता है जो एक auto_ptr
मूल्य से लौटता है , इस प्रकार auto_ptr
हर बार एक ताजा अस्थायी वस्तु का निर्माण होता है जिसे कहा जाता है। a
एक का एक उदाहरण है lvalue जबकि, make_triangle()
एक का एक उदाहरण है rvalue ।
इस तरह के रूप a
में खतरनाक है, क्योंकि हम बाद में एक सदस्य समारोह को फोन करने की कोशिश कर सकते हैं a
, अपरिभाषित व्यवहार को छोड़कर । दूसरी ओर, प्रतिद्वंद्वियों से आगे बढ़ना जैसे कि make_triangle()
पूरी तरह से सुरक्षित है, क्योंकि कॉपी कंस्ट्रक्टर ने अपना काम करने के बाद, हम अस्थायी रूप से फिर से उपयोग नहीं कर सकते हैं। कोई भी अभिव्यक्ति नहीं है जो कहा जाता है कि अस्थायी है; अगर हम बस make_triangle()
फिर से लिखते हैं , तो हमें एक अलग अस्थायी मिलता है । वास्तव में, स्थानांतरित-से-अस्थायी पहले से ही अगली पंक्ति पर चला गया है:
auto_ptr<Shape> c(make_triangle());
^ the moved-from temporary dies right here
ध्यान दें कि अक्षर l
और r
बाएं हाथ की ओर एक असाइनमेंट के दाईं ओर एक ऐतिहासिक मूल है। C ++ में यह अब सही नहीं है, क्योंकि ऐसे कार्य हैं जो किसी असाइनमेंट के बाईं ओर नहीं दिखाई दे सकते हैं (जैसे कि असाइनमेंट ऑपरेटर के बिना सरणियाँ या उपयोगकर्ता-परिभाषित प्रकार), और ऐसे नियम हैं जो (सभी प्रकार के वर्ग प्रकार) एक असाइनमेंट ऑपरेटर के साथ)।
वर्ग प्रकार का एक प्रकारांतर एक अभिव्यक्ति है जिसका मूल्यांकन एक अस्थायी वस्तु बनाता है। सामान्य परिस्थितियों में, समान दायरे के अंदर कोई अन्य अभिव्यक्ति समान अस्थायी वस्तु को दर्शाता नहीं है।
रेवले संदर्भ
अब हम समझते हैं कि lvalues से आगे बढ़ना संभावित खतरनाक है, लेकिन rvalues से चलना हानिरहित है। तो सी ++ rvalue तर्कों से lvalue तर्क भेद करने के लिए भाषा समर्थन किया था, हम या तो पूरी तरह से lvalues से आगे बढ़ न करे सकता है, या कम से कम मेकअप lvalues से आगे बढ़ पर स्पष्ट दुर्घटना से इतना है कि हम अब इस कदम कॉल स्थल पर,।
C ++ 11 की इस समस्या का उत्तर संदर्भ संदर्भ है । एक रैवल्यू संदर्भ एक नए प्रकार का संदर्भ है जो केवल प्रतिद्वंद्वियों को बांधता है, और वाक्यविन्यास है X&&
। अच्छा पुराना संदर्भ X&
अब एक लवल्यू रेफरेंस के रूप में जाना जाता है । (ध्यान दें कि X&&
है नहीं एक संदर्भ के लिए एक संदर्भ, वहाँ सी में ऐसी कोई बात नहीं है ++।)
यदि हम const
मिश्रण में फेंकते हैं , तो हमारे पास पहले से ही चार अलग-अलग प्रकार के संदर्भ हैं। वे किस प्रकार के भावों से बंध X
सकते हैं?
lvalue const lvalue rvalue const rvalue
---------------------------------------------------------
X& yes
const X& yes yes yes yes
X&& yes
const X&& yes yes
व्यवहार में, आप भूल सकते हैं const X&&
। प्रतिद्वंद्वियों से पढ़ने के लिए प्रतिबंधित होना बहुत उपयोगी नहीं है।
एक रैवल्यू संदर्भ X&&
एक नए प्रकार का संदर्भ है जो केवल प्रतिद्वंद्वियों को बांधता है।
सम्यक रूपांतरण
रेवल्यू संदर्भ कई संस्करणों के माध्यम से चला गया। संस्करण 2.1 के बाद से, एक प्रतिद्वंद्विता संदर्भ X&&
भी एक अलग प्रकार के सभी मूल्य श्रेणियों को बांधता है Y
, बशर्ते इसमें से एक अंतर्निहित रूपांतरण Y
हो X
। उस स्थिति में, एक अस्थायी प्रकार X
बनाया जाता है, और प्रतिद्वंद्विता संदर्भ उस अस्थायी से जुड़ा होता है:
void some_function(std::string&& r);
some_function("hello world");
उपरोक्त उदाहरण में, "hello world"
प्रकार का एक अंतराल है const char[12]
। के बाद से वहाँ एक अंतर्निहित रूपांतरण से है const char[12]
के माध्यम से const char*
करने के लिए std::string
, प्रकार की एक अस्थायी std::string
बनाई गई है, और r
है कि अस्थायी के लिए बाध्य है। यह उन मामलों में से एक है जहां प्रतिद्वंद्वियों (अभिव्यक्ति) और अस्थायी (वस्तुओं) के बीच का अंतर थोड़ा धुंधला है।
कंस्ट्रक्टरों को स्थानांतरित करें
एक X&&
पैरामीटर के साथ फ़ंक्शन का एक उपयोगी उदाहरण मूव कंस्ट्रक्टर है X::X(X&& source)
। इसका उद्देश्य स्रोत से प्रबंधित संसाधन के स्वामित्व को वर्तमान वस्तु में स्थानांतरित करना है।
C ++ 11 में, std::auto_ptr<T>
को प्रतिस्थापित किया गया है , std::unique_ptr<T>
जो कि रवैल्यू रेफरेंस का लाभ उठाता है। मैं एक सरलीकृत संस्करण का विकास और चर्चा करूंगा unique_ptr
। सबसे पहले, हम एक कच्चे पॉइंटर को एनकैप्सुलेट करते हैं ->
और ऑपरेटरों को अधिभारित करते हैं और *
इसलिए हमारी कक्षा एक पॉइंटर की तरह महसूस करती है:
template<typename T>
class unique_ptr
{
T* ptr;
public:
T* operator->() const
{
return ptr;
}
T& operator*() const
{
return *ptr;
}
कंस्ट्रक्टर ऑब्जेक्ट का स्वामित्व लेता है, और विध्वंसक इसे हटा देता है:
explicit unique_ptr(T* p = nullptr)
{
ptr = p;
}
~unique_ptr()
{
delete ptr;
}
अब आता है दिलचस्प हिस्सा, मूव कंस्ट्रक्टर:
unique_ptr(unique_ptr&& source) // note the rvalue reference
{
ptr = source.ptr;
source.ptr = nullptr;
}
यह मूव कंस्ट्रक्टर वही करता है जो auto_ptr
कॉपी कंस्ट्रक्टर ने किया था, लेकिन इसे केवल प्रतिद्वंद्वियों को ही दिया जा सकता है:
unique_ptr<Shape> a(new Triangle);
unique_ptr<Shape> b(a); // error
unique_ptr<Shape> c(make_triangle()); // okay
दूसरी पंक्ति संकलित करने में विफल रहती है, क्योंकि a
एक अंतराल है, लेकिन पैरामीटर unique_ptr&& source
केवल प्रतिद्वंद्वियों के लिए बाध्य हो सकता है। यह वही है जो हम चाहते थे; खतरनाक चाल कभी भी निहित नहीं होनी चाहिए। तीसरी पंक्ति बस ठीक संकलन करती है, क्योंकि make_triangle()
एक प्रतिद्वंद्विता है। चाल निर्माणकर्ता अस्थायी से स्वामित्व स्थानांतरित करेगा c
। फिर, यह वही है जो हम चाहते थे।
चाल निर्माणकर्ता एक प्रबंधित संसाधन के स्वामित्व को वर्तमान वस्तु में स्थानांतरित करता है।
असाइनमेंट ऑपरेटरों को स्थानांतरित करें
अंतिम लापता टुकड़ा चाल असाइनमेंट ऑपरेटर है। इसका काम पुराने संसाधन को जारी करना और अपने तर्क से नए संसाधन प्राप्त करना है:
unique_ptr& operator=(unique_ptr&& source) // note the rvalue reference
{
if (this != &source) // beware of self-assignment
{
delete ptr; // release the old resource
ptr = source.ptr; // acquire the new resource
source.ptr = nullptr;
}
return *this;
}
};
ध्यान दें कि चाल असाइनमेंट ऑपरेटर का यह कार्यान्वयन विध्वंसक और चाल निर्माणकर्ता दोनों के तर्क की नकल करता है। क्या आप कॉपी-एंड-स्वैप मुहावरे से परिचित हैं? इसे मूवमेंट्स को मूव-एंड-स्वैप मुहावरे के रूप में लागू करने के लिए भी लागू किया जा सकता है:
unique_ptr& operator=(unique_ptr source) // note the missing reference
{
std::swap(ptr, source.ptr);
return *this;
}
};
अब source
यह एक प्रकार का चर है unique_ptr
, इसे मूव कंस्ट्रक्टर द्वारा इनिशियलाइज़ किया जाएगा; यही है, तर्क को पैरामीटर में ले जाया जाएगा। तर्क को अभी भी एक प्रतिद्वंद्विता होने की आवश्यकता है, क्योंकि इस कदम के निर्माणकर्ता के पास एक संदर्भ संदर्भ पैरामीटर है। नियंत्रण प्रवाह के समापन ब्रेस तक पहुँच जाता है operator=
, source
क्षेत्र से बाहर चला जाता है, पुराने संसाधन स्वचालित रूप से रिलीज़ किया था।
चाल असाइनमेंट ऑपरेटर एक प्रबंधित संसाधन के स्वामित्व को वर्तमान ऑब्जेक्ट में स्थानांतरित करता है, पुराने संसाधन को जारी करता है। चाल और अदला-बदली मुहावरा कार्यान्वयन को सरल बनाता है।
लवलीन से चल रहा है
कभी-कभी, हम lvalues से आगे बढ़ना चाहते हैं। यही है, कभी-कभी हम चाहते हैं कि कंपाइलर एक लवल्यू का इलाज करे जैसे कि वह एक रव्वा था, इसलिए यह मूव कंस्ट्रक्टर को आमंत्रित कर सकता है, भले ही यह संभावित असुरक्षित हो सकता है। इस उद्देश्य के लिए, C ++ 11 std::move
हेडर के अंदर एक मानक पुस्तकालय फ़ंक्शन टेम्पलेट प्रदान करता है <utility>
। यह नाम थोड़ा दुर्भाग्यपूर्ण है, क्योंकि std::move
बस एक प्रतिद्वंद्विता के लिए एक झूठ बोलता है; यह अपने आप कुछ नहीं ले जाता है। यह केवल गति करने में सक्षम बनाता है । शायद यह नाम दिया जाना चाहिए किया गया है std::cast_to_rvalue
या std::enable_move
है, लेकिन हम अब तक नाम के साथ फंस रहे हैं।
यहाँ बताया गया है कि आप स्पष्ट रूप से एक अंतराल से चलते हैं:
unique_ptr<Shape> a(new Triangle);
unique_ptr<Shape> b(a); // still an error
unique_ptr<Shape> c(std::move(a)); // okay
ध्यान दें कि तीसरी पंक्ति के बाद, a
अब एक त्रिकोण का मालिक नहीं है। यह ठीक है, क्योंकि स्पष्ट रूप से लिखने से std::move(a)
, हमने अपने इरादे स्पष्ट कर दिए: "प्रिय निर्माता, a
आरंभ करने के लिए जो कुछ भी आप चाहते हैं c
, मैं करता हूं; मुझे a
अब कोई परवाह नहीं है। अपने तरीके से स्वतंत्र महसूस करें a
।"
std::move(some_lvalue)
इस तरह एक बाद के कदम को सक्षम करने के लिए, एक प्रतिद्वंद्विता के लिए एक अंतराल देता है।
XValues
ध्यान दें कि भले ही std::move(a)
एक प्रतिद्वंद्विता हो, लेकिन इसका मूल्यांकन एक अस्थायी वस्तु नहीं बनाता है। इस कोन्ड्रम ने समिति को तीसरी मूल्य श्रेणी लाने के लिए मजबूर किया। कुछ ऐसा है जो एक पारंपरिक संदर्भ में नहीं हो सकता है, भले ही यह पारंपरिक अर्थों में एक प्रतिद्वंद्विता क्यों न हो , इसे एक xvalue (eXpiring value) कहा जाता है । पारंपरिक प्रतिद्वंद्वियों का नाम बदलकर प्रचलन (शुद्ध नियम) कर दिया गया ।
प्रचलन और xvalues दोनों ही प्रचलन हैं। Xvalues और lvalues दोनों glvalues (Generalized lvalues) हैं। आरेख के साथ रिश्तों को समझना आसान है:
expressions
/ \
/ \
/ \
glvalues rvalues
/ \ / \
/ \ / \
/ \ / \
lvalues xvalues prvalues
ध्यान दें कि केवल xvalues वास्तव में नए हैं; बाकी सिर्फ नाम बदलने और समूहीकरण के कारण है।
C ++ 11 में C ++ 98 प्रचलन को प्रचलन के रूप में जाना जाता है। मानसिक रूप से "व्याप्त" की सभी घटनाओं को पूर्ववर्ती पैराग्राफों में "प्रचलन" से बदल दें।
कार्यों से बाहर जा रहा है
अब तक, हमने स्थानीय चर और फ़ंक्शन मापदंडों में आंदोलन को देखा है। लेकिन विपरीत दिशा में भी चलना संभव है। यदि कोई फ़ंक्शन मान से वापस आता है, तो कॉल साइट पर कुछ ऑब्जेक्ट (शायद एक स्थानीय चर या एक अस्थायी, लेकिन किसी भी तरह की वस्तु हो सकती है) को return
बयान के साथ शुरू किया जाता है, क्योंकि कदम निर्माता के तर्क के रूप में बयान के बाद :
unique_ptr<Shape> make_triangle()
{
return unique_ptr<Shape>(new Triangle);
} \-----------------------------/
|
| temporary is moved into c
|
v
unique_ptr<Shape> c(make_triangle());
शायद आश्चर्य की बात, स्वत: वस्तुओं (स्थानीय चर कि के रूप में घोषित नहीं कर रहे हैं static
) भी किया जा सकता है परोक्ष कार्यों से बाहर चले गए:
unique_ptr<Shape> make_square()
{
unique_ptr<Shape> result(new Square);
return result; // note the missing std::move
}
कैसे आता है कदम निर्माता result
तर्क के रूप में अंतराल को स्वीकार करता है? का दायरा result
समाप्त होने वाला है, और स्टैक अनइंडिंग के दौरान यह नष्ट हो जाएगा। कोई भी संभवतः बाद में शिकायत नहीं कर सकता था जो result
किसी भी तरह बदल गया था; जब नियंत्रण प्रवाह वापस कॉलर पर है, result
अब मौजूद नहीं है! इस कारण से, C ++ 11 में एक विशेष नियम है, जो बिना लिखने के कार्यों से स्वचालित वस्तुओं को वापस करने की अनुमति देता है std::move
। वास्तव में, आपको स्वचालित ऑब्जेक्ट्स को फ़ंक्शंस से बाहर जाने के लिए उपयोग नहीं करना चाहिए std::move
, क्योंकि यह "नाम वापसी मूल्य अनुकूलन" (NRVO) को रोकता है।
std::move
स्वचालित ऑब्जेक्ट्स को फ़ंक्शंस से बाहर ले जाने के लिए कभी भी उपयोग न करें ।
ध्यान दें कि दोनों कारखाने के कार्यों में, वापसी प्रकार एक मूल्य है, न कि एक संदर्भ संदर्भ। संदर्भ संदर्भ अभी भी संदर्भ हैं, और हमेशा की तरह, आपको कभी भी स्वचालित वस्तु का संदर्भ नहीं देना चाहिए; यदि आप संकलक को अपने कोड को स्वीकार करने में धोखा देते हैं, तो कॉलर झूलते हुए संदर्भ के साथ समाप्त होगा:
unique_ptr<Shape>&& flawed_attempt() // DO NOT DO THIS!
{
unique_ptr<Shape> very_bad_idea(new Square);
return std::move(very_bad_idea); // WRONG!
}
कभी भी स्वचालित वस्तुओं को संदर्भ के संदर्भ में न लौटाएँ। मूव एक्सक्लूसिव रूप से मूव कंस्ट्रक्टर द्वारा मूव किया जाता है, न कि std::move
रिविल्यू रेफरेंस से केवल एक रव्वा को बांधकर।
सदस्यों में चल रहा है
जल्दी या बाद में, आप इस तरह कोड लिखने जा रहे हैं:
class Foo
{
unique_ptr<Shape> member;
public:
Foo(unique_ptr<Shape>&& parameter)
: member(parameter) // error
{}
};
मूल रूप से, कंपाइलर शिकायत करेगा कि parameter
एक अंतराल है। यदि आप इसके प्रकार को देखते हैं, तो आप एक रवैल्यू रेफरेंस देखते हैं, लेकिन एक रेवल्यू रेफरेंस का अर्थ है "एक रेव्यू के लिए बाध्य होने वाला संदर्भ"; इसका मतलब यह नहीं है कि संदर्भ ही एक प्रतिद्वंद्विता है! वास्तव में, parameter
नाम के साथ सिर्फ एक साधारण चर है। आप parameter
कंस्ट्रक्टर के शरीर के अंदर जितनी बार चाहें उपयोग कर सकते हैं , और यह हमेशा एक ही वस्तु को दर्शाता है। इससे स्पष्ट रूप से आगे बढ़ना खतरनाक होगा, इसलिए भाषा इसे मना करती है।
एक नामांकित संदर्भ संदर्भ किसी अन्य चर की तरह एक अंतराल है।
समाधान मैन्युअल रूप से इस कदम को सक्षम करने के लिए है:
class Foo
{
unique_ptr<Shape> member;
public:
Foo(unique_ptr<Shape>&& parameter)
: member(std::move(parameter)) // note the std::move
{}
};
आप यह तर्क दे सकते हैं कि parameter
इनका उपयोग शुरू होने के बाद अब नहीं किया जाता है member
। std::move
केवल रिटर्न वैल्यू के साथ चुपचाप डालने का कोई विशेष नियम क्यों नहीं है ? शायद इसलिए, क्योंकि यह कंपाइलर कार्यान्वयनकर्ताओं पर बहुत अधिक बोझ होगा। उदाहरण के लिए, क्या होगा यदि निर्माणकर्ता निकाय किसी अन्य अनुवाद इकाई में था? इसके विपरीत, वापसी मान नियम को केवल यह निर्धारित करने के लिए प्रतीक तालिकाओं की जांच करनी है कि return
कीवर्ड स्वत: ऑब्जेक्ट को दर्शाता है या नहीं ।
आप parameter
मूल्य से भी गुजर सकते हैं । मूव-ओनली टाइप्स के लिए unique_ptr
, ऐसा लगता है कि अभी तक कोई स्थापित मुहावरा नहीं है। व्यक्तिगत रूप से, मैं मूल्य से पारित करना पसंद करता हूं, क्योंकि यह इंटरफ़ेस में कम अव्यवस्था का कारण बनता है।
विशेष सदस्य के कार्य
C ++ 98 स्पष्ट रूप से मांग पर तीन विशेष सदस्य कार्यों की घोषणा करता है, जब उन्हें कहीं जरूरत होती है: कॉपी कंस्ट्रक्टर, कॉपी असाइनमेंट ऑपरेटर और डिस्ट्रक्टर।
X::X(const X&); // copy constructor
X& X::operator=(const X&); // copy assignment operator
X::~X(); // destructor
रेवल्यू संदर्भ कई संस्करणों के माध्यम से चला गया। संस्करण 3.0 के बाद से, C ++ 11 मांग पर दो अतिरिक्त विशेष सदस्य कार्य करता है: मूव कंस्ट्रक्टर और मूव असाइनमेंट ऑपरेटर। ध्यान दें कि न तो VC10 और न ही VC11 अभी तक 3.0 संस्करण के अनुरूप हैं, इसलिए आपको उन्हें स्वयं लागू करना होगा।
X::X(X&&); // move constructor
X& X::operator=(X&&); // move assignment operator
इन दो नए विशेष सदस्य कार्यों को केवल तब ही घोषित किया जाता है जब विशेष सदस्य कार्यों में से कोई भी मैन्युअल रूप से घोषित नहीं किया जाता है। इसके अलावा, यदि आप अपने स्वयं के मूव कंस्ट्रक्टर या मूव असाइनमेंट ऑपरेटर की घोषणा करते हैं, तो न तो कॉपी कंस्ट्रक्टर और न ही कॉपी असाइनमेंट ऑपरेटर को स्पष्ट रूप से घोषित किया जाएगा।
व्यवहार में इन नियमों का क्या मतलब है?
यदि आप अप्रबंधित संसाधनों के बिना एक वर्ग लिखते हैं, तो पांच विशेष सदस्य कार्यों में से किसी को भी स्वयं घोषित करने की आवश्यकता नहीं है, और आपको सही कॉपी शब्दार्थ मिलेगा और मुफ्त में शब्दार्थ को स्थानांतरित करना होगा। अन्यथा, आपको विशेष सदस्य कार्यों को स्वयं लागू करना होगा। बेशक, यदि आपकी कक्षा चालित शब्दार्थ से लाभ नहीं उठाती है, तो विशेष कदम संचालन को लागू करने की कोई आवश्यकता नहीं है।
ध्यान दें कि कॉपी असाइनमेंट ऑपरेटर और मूव असाइनमेंट ऑपरेटर को सिंगल, यूनिफाइड असाइनमेंट ऑपरेटर में फ्यूज किया जा सकता है, मान द्वारा इसकी समीक्षा करें:
X& X::operator=(X source) // unified assignment operator
{
swap(source); // see my first answer for an explanation
return *this;
}
इस तरह, विशेष सदस्य की संख्या पांच से चार तक की बूंदों को लागू करने के लिए कार्य करती है। यहां अपवाद-सुरक्षा और दक्षता के बीच एक व्यापार है, लेकिन मैं इस मुद्दे पर विशेषज्ञ नहीं हूं।
अग्रेषण संदर्भ ( पहले सार्वभौमिक संदर्भ के रूप में जाना जाता है )
निम्नलिखित फ़ंक्शन टेम्पलेट पर विचार करें:
template<typename T>
void foo(T&&);
आप T&&
केवल प्रतिद्वंद्वियों से बंधने की उम्मीद कर सकते हैं , क्योंकि पहली नज़र में, यह एक संदर्भ के संदर्भ की तरह दिखता है। हालांकि यह पता चला है, T&&
यह भी अंतराल को बांधता है:
foo(make_triangle()); // T is unique_ptr<Shape>, T&& is unique_ptr<Shape>&&
unique_ptr<Shape> a(new Triangle);
foo(a); // T is unique_ptr<Shape>&, T&& is unique_ptr<Shape>&
यदि तर्क प्रकार का एक खंड है X
, T
तो होने का मतलब है X
, इसलिए T&&
इसका मतलब है X&&
। यह वही है जो कोई भी उम्मीद करेगा। लेकिन अगर तर्क X
एक विशेष नियम के कारण, प्रकार का एक प्रकार है , तो होने के लिए कटौती की T
जाती है X&
, इसलिए T&&
इसका मतलब कुछ ऐसा होगा X& &&
। लेकिन चूंकि C ++ में अभी भी संदर्भों के संदर्भ में कोई धारणा नहीं है, इसलिए टाइप X& &&
को ढहा दिया गया है X&
। यह पहली बार में भ्रामक और बेकार लग सकता है, लेकिन परफेक्ट फ़ॉरवर्डिंग के लिए संदर्भ ढहना आवश्यक है (जिसकी चर्चा यहां नहीं की जाएगी)।
T && एक संदर्भ संदर्भ नहीं है, बल्कि एक अग्रेषण संदर्भ है। यह भी अंतराल को बांधता है, जिस स्थिति में T
और T&&
दोनों अंतराल संदर्भ हैं।
यदि आप किसी फ़ंक्शन टेम्प्लेट को प्रतिद्वंद्वियों के लिए बाध्य करना चाहते हैं, तो आप SFINAE को टाइप लक्षणों के साथ जोड़ सकते हैं :
#include <type_traits>
template<typename T>
typename std::enable_if<std::is_rvalue_reference<T&&>::value, void>::type
foo(T&&);
चाल का क्रियान्वयन
अब जब आप संदर्भ ढहने को समझते हैं, तो यहां बताया गया std::move
है:
template<typename T>
typename std::remove_reference<T>::type&&
move(T&& t)
{
return static_cast<typename std::remove_reference<T>::type&&>(t);
}
जैसा कि आप देख सकते हैं, move
अग्रेषण संदर्भ के लिए किसी भी प्रकार के पैरामीटर को स्वीकार करता है T&&
, और यह एक रवैल्यू संदर्भ देता है। std::remove_reference<T>::type
क्योंकि अन्यथा, प्रकार की lvalues के लिए मेटा-समारोह कॉल के लिए आवश्यक है X
, वापसी प्रकार होगा X& &&
, जो टुकड़ों में टूट जाएगा X&
। चूँकि t
हमेशा एक अंतराल है (याद रखें कि एक नामित रूवल संदर्भ एक अंतराल है), लेकिन हम t
एक अंतराल संदर्भ को बांधना चाहते हैं , हमें स्पष्ट रूप t
से सही रिटर्न प्रकार में डालना होगा। एक फ़ंक्शन का कॉल जो एक रैवल्यू संदर्भ देता है, वह स्वयं एक xvalue है। अब आप जानते हैं कि xvalues कहाँ से आते हैं;)
एक फ़ंक्शन का कॉल std::move
, जो एक रैवल्यू संदर्भ देता है, जैसे कि , एक xvalue है।
ध्यान दें कि प्रतिद्वंद्विता संदर्भ द्वारा वापस लौटना इस उदाहरण में ठीक है, क्योंकि t
एक स्वचालित वस्तु को निरूपित नहीं करता है, बल्कि कॉल करने वाले द्वारा पारित की गई वस्तु के बजाय।