फ़ंक्शन से अनूठे_ptr को वापस करना


367

unique_ptr<T>प्रतिलिपि निर्माण की अनुमति नहीं देता है, इसके बजाय यह चाल शब्दार्थों का समर्थन करता है। फिर भी, मैं unique_ptr<T>एक फ़ंक्शन से वापस आ सकता हूं और लौटाए गए मान को एक चर में असाइन कर सकता हूं ।

#include <iostream>
#include <memory>

using namespace std;

unique_ptr<int> foo()
{
  unique_ptr<int> p( new int(10) );

  return p;                   // 1
  //return move( p );         // 2
}

int main()
{
  unique_ptr<int> p = foo();

  cout << *p << endl;
  return 0;
}

कोड ऊपर संकलित है और इरादा के अनुसार काम करता है। तो यह कैसे होता है कि लाइन 1कॉपी कंस्ट्रक्टर को आमंत्रित नहीं करती है और संकलक त्रुटियों में परिणाम होता है? अगर मुझे लाइन का उपयोग 2करना था तो इसका मतलब होगा (लाइन 2काम करता है साथ ही, लेकिन हमें ऐसा करने की आवश्यकता नहीं है)।

मुझे पता है कि C ++ 0x इस अपवाद की अनुमति देता है unique_ptrक्योंकि रिटर्न वैल्यू एक अस्थायी ऑब्जेक्ट है जो कि फ़ंक्शन से बाहर निकलते ही नष्ट हो जाएगा, इस प्रकार रिटर्न पॉइंटर की विशिष्टता की गारंटी देता है। मैं इस बारे में उत्सुक हूं कि यह कैसे लागू किया जाता है, क्या यह संकलक में विशेष आवरण है या क्या भाषा विनिर्देश में कुछ अन्य खंड है जो इस शोषण को दर्शाता है?


हाइपोथेटिक रूप से, यदि आप एक कारखाना विधि लागू कर रहे थे, तो क्या आप कारखाने का उत्पादन वापस करने के लिए १ या २ पसंद करेंगे? मुझे लगता है कि यह 1 का सबसे आम उपयोग होगा, क्योंकि एक उचित कारखाने के साथ, आप वास्तव में कॉलर को पास करने के लिए निर्मित चीज का स्वामित्व चाहते हैं।
Xharlie

7
@Xharlie? वे दोनों के स्वामित्व को पारित करते हैं unique_ptr। पूरा प्रश्न 1 और 2 एक ही चीज़ को प्राप्त करने के दो अलग-अलग तरीके हैं।
प्रेटोरियन

इस मामले में, RVO c ++ 0x में भी होता है, अनूठे_ptr ऑब्जेक्ट का विनाश एक बार होगा जो mainफ़ंक्शन के बाहर निकलने के बाद किया जाता है, लेकिन बाहर निकलने पर नहीं foo
amp17d

जवाबों:


218

क्या भाषा विनिर्देश में कुछ अन्य खंड हैं जो इस कारनामे को दर्शाते हैं?

हां, 12.8 §34 और see35 देखें:

जब कुछ मानदंडों को पूरा किया जाता है, तो एक कार्यान्वयन को किसी क्लास ऑब्जेक्ट की कॉपी / चाल निर्माण को छोड़ने की अनुमति दी जाती है [...] प्रतिलिपि / चालित संचालन की यह प्रक्रिया, जिसे कॉपी एलिसन कहा जाता है , की अनुमति है [...] एक वर्ग वापसी प्रकार के साथ एक फ़ंक्शन, जब अभिव्यक्ति एक गैर-वाष्पशील स्वचालित वस्तु का नाम है, जो उसी cv-unqualified प्रकार के साथ फ़ंक्शन रिटर्न प्रकार [...]

जब एक कॉपी ऑपरेशन की समाप्ति के मानदंड पूरे किए जाते हैं और कॉपी की जाने वाली वस्तु को एक लेवल्यू द्वारा नामित किया जाता है, तो कॉपी के लिए कंस्ट्रक्टर का चयन करने के लिए अधिभार संकल्प को पहले प्रदर्शन किया जाता है जैसे कि ऑब्जेक्ट को किसी रिवैल्यू द्वारा नामित किया गया था


बस एक और बिंदु जोड़ना चाहता था कि मूल्य द्वारा वापसी यहां डिफ़ॉल्ट विकल्प होना चाहिए क्योंकि सबसे खराब स्थिति में रिटर्न स्टेटमेंट में एक नामित मूल्य, अर्थात बिना C ++ 11, C ++ 14 और C ++ 17 में बिना किसी योग्यता के इलाज किया जाता है एक प्रतिद्वंद्विता के रूप में। इसलिए उदाहरण के लिए निम्नलिखित समारोह -fno-elide-constructorsध्वज के साथ संकलित है

std::unique_ptr<int> get_unique() {
  auto ptr = std::unique_ptr<int>{new int{2}}; // <- 1
  return ptr; // <- 2, moved into the to be returned unique_ptr
}

...

auto int_uptr = get_unique(); // <- 3

संकलन पर ध्वज सेट के साथ इस फ़ंक्शन में दो चालें (1 और 2) हो रही हैं और फिर बाद में (3) पर एक चाल चल रही है।


@juanchopanza क्या आप अनिवार्य रूप से मतलब है कि foo()वास्तव में भी नष्ट होने के बारे में है (अगर यह कुछ भी नहीं सौंपा गया था), फ़ंक्शन के भीतर वापसी मूल्य की तरह, और इसलिए यह समझ में आता है कि C ++ एक मूव कंस्ट्रक्टर का उपयोग करते समय करता है unique_ptr<int> p = foo();?
7

1
यह उत्तर कहता है कि किसी कार्यान्वयन को कुछ करने की अनुमति है ... यह नहीं कहता है कि ऐसा होना चाहिए, इसलिए यदि यह एकमात्र प्रासंगिक अनुभाग था, तो यह इस व्यवहार पर निर्भर होगा कि पोर्टेबल नहीं है। लेकिन मुझे नहीं लगता कि यह सही है। मुझे लगता है कि सही उत्तर चाल निर्माणकर्ता के साथ करने के लिए अधिक इच्छुक है, जैसा कि निकोला स्माइलजेनिक और बार्टोज़ मिल्वेस्की के उत्तर में वर्णित है।
डॉन हैच

6
@ डॉन्चैच का कहना है कि उन मामलों में एलिसन की प्रतिलिपि बनाने / स्थानांतरित करने के लिए इसे "अनुमति" है, लेकिन हम यहां कॉपी एलिसन के बारे में बात नहीं कर रहे हैं। यह दूसरा उद्धृत पैराग्राफ है जो यहां लागू होता है, जो कॉपी एलिगेंस नियमों पर गुल्लक-पीठ करता है, लेकिन खुद को एलिसन कॉपी नहीं करता है। दूसरे पैराग्राफ में कोई अनिश्चितता नहीं है - यह पूरी तरह से पोर्टेबल है।
जोसेफ मैन्सफील्ड

@juanchopanza मुझे एहसास है कि यह अब 2 साल बाद है, लेकिन क्या आपको अभी भी लगता है कि यह गलत है? जैसा कि मैंने पिछली टिप्पणी में उल्लेख किया है, यह कॉपी एलिसन के बारे में नहीं है। यह सिर्फ इतना होता है कि उन मामलों में जहां कॉपी एलिसन लागू हो सकता है (भले ही वह इसके साथ आवेदन नहीं कर सकता है std::unique_ptr), पहले वस्तुओं को फिर से इलाज करने के लिए एक विशेष नियम है। मुझे लगता है कि निकोला ने जो जवाब दिया है, उससे यह पूरी तरह सहमत है।
जोसेफ मैन्सफील्ड

1
तो मैं अभी भी अपने कदम-ही प्रकार (हटाए गए प्रतिलिपि निर्माता) के लिए "हटाए गए फ़ंक्शन को संदर्भित करने का प्रयास" करने में त्रुटि क्यों प्राप्त करता हूं जब इसे इस उदाहरण के समान तरीके से वापस कर रहा हूं?
शाम

104

यह std::unique_ptrकिसी भी तरह से विशिष्ट नहीं है , लेकिन चल सकने योग्य किसी भी वर्ग पर लागू होता है। यह भाषा के नियमों द्वारा गारंटीकृत है क्योंकि आप मूल्य से लौट रहे हैं। कंपाइलर कॉपियों को अलग करने की कोशिश करता है, एक मूव कंस्ट्रक्टर को इनवॉइस करता है अगर वह कॉपियाँ नहीं निकाल सकता है, एक कॉपी कंस्ट्रक्टर को कॉल करता है अगर वह मूव नहीं कर सकता है, और अगर वह कॉपी नहीं कर सकता है तो कंपाइल करने में विफल रहता है।

यदि आपके पास एक फ़ंक्शन है जो std::unique_ptrएक तर्क के रूप में स्वीकार करता है तो आप इसे पी पास नहीं कर पाएंगे। आपको स्पष्ट रूप से मूव कंस्ट्रक्टर को इनवॉइस करना होगा, लेकिन इस स्थिति में आपको कॉल के बाद वेरिएबल पी का उपयोग नहीं करना चाहिए bar()

void bar(std::unique_ptr<int> p)
{
    // ...
}

int main()
{
    unique_ptr<int> p = foo();
    bar(p); // error, can't implicitly invoke move constructor on lvalue
    bar(std::move(p)); // OK but don't use p afterwards
    return 0;
}

3
@ तैयार - ठीक है, वास्तव में नहीं। हालांकि pएक अस्थायी नहीं है, का परिणाम है foo(), जो लौटा जा रहा है, है; इस प्रकार यह एक प्रतिद्वंद्विता है और इसे स्थानांतरित किया जा सकता है, जो mainसंभव में असाइनमेंट बनाता है। मैं कहता हूं कि आप गलत थे सिवाय इसके कि निकोला तब इस नियम को pस्वयं लागू करता है जो गलती से आईएस है।
एडवर्ड स्ट्रेंज

वास्तव में मैं क्या कहना चाहता था, लेकिन शब्दों को नहीं पा सका। मैंने उत्तर के उस हिस्से को हटा दिया है क्योंकि यह बहुत स्पष्ट नहीं था।
निकोला स्मिलजनीक

मेरा एक प्रश्न है: मूल प्रश्न में, क्या रेखा 1और रेखा के बीच कोई पर्याप्त अंतर है 2? मेरे विचार pमें main, निर्माण के बाद से यह एक ही है , यह केवल वापसी के प्रकार के बारे में परवाह करता है foo, है ना?
होंग्क्सू चेन

1
@HongxuChen उस उदाहरण में बिल्कुल कोई अंतर नहीं है, स्वीकृत उत्तर में मानक से उद्धरण देखें।
निकोला स्मिलजनीक सिप

दरअसल, जब तक आप इसे असाइन करते हैं, तब तक आप पी का उपयोग कर सकते हैं। तब तक, आप सामग्री को संदर्भित करने का प्रयास नहीं कर सकते।
एलन

38

unique_ptr में पारंपरिक कॉपी कंस्ट्रक्टर नहीं है। इसके बजाय इसमें एक "मूव कंस्ट्रक्टर" है जो रेवल्यू रेफरेंस का उपयोग करता है:

unique_ptr::unique_ptr(unique_ptr && src);

एक रवैल्यू रेफरेंस (डबल एम्परसेंड) केवल एक रवैल्यू से बंधेगा। इसीलिए जब आप किसी फ़ंक्शन के लिए एक lvalue unique_ptr पास करने का प्रयास करते हैं तो आपको एक त्रुटि मिलती है। दूसरी ओर, एक फ़ंक्शन से लौटाए गए मान को एक प्रतिद्वंद्विता के रूप में माना जाता है, इसलिए चाल निर्माणकर्ता को स्वचालित रूप से कहा जाता है।

वैसे, यह सही ढंग से काम करेगा:

bar(unique_ptr<int>(new int(44));

अस्थायी अनूठे_ptr यहाँ एक प्रतिद्वंद्विता है।


8
मुझे लगता है कि बिंदु अधिक है, क्यों p- "जाहिर है" एक अंतराल - की परिभाषा में वापसी के बयान में एक प्रतिद्वंद्विता के रूप में माना जा सकता return p;है foo। मुझे नहीं लगता कि इस तथ्य के साथ कोई मुद्दा है कि फ़ंक्शन का वापसी मूल्य "स्थानांतरित" हो सकता है।
सीबी बेली

क्या फ़ंक्शन से दिए गए मान को std में लपेटना :: चाल का मतलब है कि इसे दो बार स्थानांतरित किया जाएगा?

3
@RodrigoSalazar std :: चाल सिर्फ एक फैंसी संदर्भ से एक संदर्भ (और) से एक प्रतिद्वंद्वी संदर्भ (&&) है। एसटीडी का अत्यधिक उपयोग :: एक प्रतिद्वंद्विता संदर्भ पर कदम बस एक noop होगा
TiMoch

13

मुझे लगता है कि यह स्कॉट मेयर्स के प्रभावी आधुनिक सी ++ के आइटम 25 में पूरी तरह से समझाया गया है । यहाँ एक अंश है:

आरवीओ को आशीर्वाद देने वाले मानक का हिस्सा यह कहता है कि यदि आरवीओ के लिए शर्तें पूरी हो जाती हैं, लेकिन कंपाइलर कॉपी एलीशन नहीं करते हैं, तो लौटाए जाने वाले ऑब्जेक्ट को एक प्रतिद्वंद्विता माना जाना चाहिए। वास्तव में, मानक की आवश्यकता होती है कि जब आरवीओ को अनुमति दी जाती है, तो या तो नकल की जगह बनती है या std::moveस्थानीय वस्तुओं पर लौटा दी जाती है।

यहाँ, आरवीओ मान मूल्य अनुकूलन वापस करने के लिए संदर्भित करता है , और अगर आरवीओ के लिए शर्तें पूरी होती हैं, तो फ़ंक्शन के अंदर घोषित स्थानीय ऑब्जेक्ट को वापस करने का मतलब है कि आप आरवीओ करने की उम्मीद करेंगे , जिसे उनकी पुस्तक के आइटम 25 में भी अच्छी तरह से समझाया गया है। मानक (यहां स्थानीय वस्तु में रिटर्न स्टेटमेंट द्वारा बनाई गई अस्थायी वस्तुएं शामिल हैं)। अंश से सबसे बड़ा ले लो या तो कॉपी एलिसन होता है या std::moveस्थानीय रूप से वापस लौटाए जा रहे स्थानीय वस्तुओं पर लगाया जाता है । मद 25 में स्कॉट का उल्लेख std::moveकिया गया है जो संकलक द्वारा लागू किए जाने पर लागू नहीं होता है और प्रोग्रामर स्पष्ट रूप से ऐसा नहीं करता है।

आपके मामले में, कोड स्पष्ट रूप से आरवीओ के लिए एक उम्मीदवार है क्योंकि यह स्थानीय वस्तु को लौटाता है pऔर इसका प्रकार pरिटर्न प्रकार के समान है, जिसके परिणामस्वरूप कॉपी एलिसेंस होता है। और अगर कंपाइलर कॉपी को इलीट नहीं करता है, तो जिस भी कारण से, std::moveलाइन में लगा होगा 1


5

एक बात जो मैंने अन्य उत्तरों में नहीं देखी, वह हैएक और उत्तर को स्पष्ट करने के लिए कि रिटर्निंग std :: unique_ptr के बीच एक अंतर है जो एक फ़ंक्शन के भीतर बनाया गया है, और एक जो उस फ़ंक्शन को दिया गया है।

उदाहरण इस प्रकार हो सकता है:

class Test
{int i;};
std::unique_ptr<Test> foo1()
{
    std::unique_ptr<Test> res(new Test);
    return res;
}
std::unique_ptr<Test> foo2(std::unique_ptr<Test>&& t)
{
    // return t;  // this will produce an error!
    return std::move(t);
}

//...
auto test1=foo1();
auto test2=foo2(std::unique_ptr<Test>(new Test));

यह fredoverflow द्वारा जवाब में उल्लेख किया है - स्पष्ट रूप से " स्वचालित वस्तु" पर प्रकाश डाला । एक संदर्भ (एक रेवल्यू संदर्भ सहित) एक स्वचालित वस्तु नहीं है।
टोबी स्पाइट

@TobySpeight ठीक है, क्षमा करें। मुझे लगता है कि मेरा कोड अभी एक स्पष्टीकरण है।
v010dya
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.