क्या हम C ++ सुविधाओं का उपयोग करके इस समस्या को हल कर सकते हैं?
C ++ 11 ने सदस्य फ़ंक्शन रेफ-क्वालीफायर को जोड़ा है, जो वर्ग के उदाहरण (अभिव्यक्ति) के मूल्य श्रेणी को प्रतिबंधित करने की अनुमति देता है, जिस पर सदस्य फ़ंक्शन को बुलाया जा सकता है। उदाहरण के लिए:
struct foo {
void bar() & {} // lvalue-ref-qualified
};
foo& lvalue ();
foo prvalue();
lvalue ().bar(); // OK
prvalue().bar(); // error
begin
सदस्य फ़ंक्शन को कॉल करते समय , हम जानते हैं कि सबसे अधिक संभावना है कि हमें end
सदस्य फ़ंक्शन को कॉल करने की आवश्यकता होगी (या size
सीमा के आकार को प्राप्त करने के लिए ऐसा कुछ )। इसके लिए आवश्यक है कि हम एक अंतराल पर काम करें, क्योंकि हमें इसे दो बार संबोधित करने की आवश्यकता है। इसलिए आप तर्क दे सकते हैं कि इन सदस्य कार्यों को लंबू-रेफ-योग्य होना चाहिए।
हालाँकि, यह अंतर्निहित समस्या को हल नहीं कर सकता है: अलियासिंग। begin
और end
सदस्य समारोह उर्फ वस्तु, या संसाधनों वस्तु द्वारा प्रबंधित। यदि हम बदलते हैं begin
और end
एक ही फ़ंक्शन द्वारा range
, हमें एक प्रदान करना चाहिए, जिसे rvalues पर बुलाया जा सकता है:
struct foo {
vector<int> arr;
auto range() & // C++14 return type deduction for brevity
{ return std::make_pair(arr.begin(), arr.end()); }
};
for(auto const& e : foo().range()) // error
यह एक वैध उपयोग मामला हो सकता है, लेकिन उपरोक्त परिभाषा range
इसे अस्वीकार करती है। चूंकि हम सदस्य फ़ंक्शन कॉल के बाद अस्थायी को संबोधित नहीं कर सकते हैं, इसलिए कंटेनर को वापस करने के लिए यह अधिक उचित हो सकता है, अर्थात एक स्वामित्व सीमा:
struct foo {
vector<int> arr;
auto range() &
{ return std::make_pair(arr.begin(), arr.end()); }
auto range() &&
{ return std::move(arr); }
};
for(auto const& e : foo().range()) // OK
यह ओपी के मामले में लागू होता है, और मामूली कोड की समीक्षा
struct B {
A m_a;
A & a() { return m_a; }
};
यह सदस्य फ़ंक्शन अभिव्यक्ति के मूल्य श्रेणी को बदलता है: B()
एक प्रचलन है, लेकिन एक अंतराल B().a()
है। दूसरी ओर, B().m_a
एक प्रतिद्वंद्विता है। तो चलिए इसे सुसंगत बनाकर शुरू करते हैं। इसे करने के दो तरीके हैं:
struct B {
A m_a;
A & a() & { return m_a; }
A && a() && { return std::move(m_a); }
// or
A a() && { return std::move(m_a); }
};
दूसरा संस्करण, जैसा कि ऊपर कहा गया है, ओपी में समस्या को ठीक करेगा।
इसके अतिरिक्त, हम B
सदस्य के कार्यों को प्रतिबंधित कर सकते हैं :
struct A {
// [...]
int * begin() & { return &v[0]; }
int * end () & { return &v[3]; }
int v[3];
};
ओपी के कोड पर इसका कोई प्रभाव नहीं पड़ेगा, क्योंकि :
लूप के लिए रेंज-आधारित में अभिव्यक्ति का परिणाम एक संदर्भ चर के लिए बाध्य है। और यह चर (इसके begin
और end
सदस्य कार्यों तक पहुंचने के लिए उपयोग की जाने वाली अभिव्यक्ति के रूप में ) एक अंतराल है।
बेशक, सवाल यह है कि क्या डिफ़ॉल्ट नियम "प्रतिद्वंद्वियों पर सदस्य का कार्य करना " होना चाहिए, जब तक कि उसके सभी संसाधनों का मालिक एक वस्तु वापस नहीं आनी चाहिए, जब तक कि कोई अच्छा कारण न हो । यह जिस उपनाम से वापस आता है वह कानूनी रूप से उपयोग किया जा सकता है, लेकिन आपके द्वारा अनुभव किए जा रहे तरीके से खतरनाक है: इसका उपयोग अपने "माता-पिता" के जीवनकाल को अस्थायी रूप से बढ़ाने के लिए नहीं किया जा सकता है:
// using the OP's definition of `struct B`,
// or version 1, `A && a() &&;`
A&& a = B().a(); // bug: binds directly, dangling reference
A const& a = B().a(); // bug: same as above
A a = B().a(); // OK
A&& a = B().m_a; // OK: extends the lifetime of the temporary
C ++ 2a में, मुझे लगता है कि आप इस (या इसी तरह) के आसपास काम करने वाले हैं जो निम्नानुसार है:
for( B b = bar(); auto i : b.a() )
ओपी के बजाय
for( auto i : bar().a() )
वर्कअराउंड मैन्युअल रूप से निर्दिष्ट करता है कि जीवनकाल b
फॉर-लूप का संपूर्ण ब्लॉक है।
प्रस्ताव जो इस init-statement को पेश करता है
लाइव डेमो