मैं एक कंस्ट्रक्टर या फ़ंक्शन के लिए एक unique_ptr तर्क कैसे पास करूं?


400

मैं C ++ 11 में शब्दार्थ को स्थानांतरित करने के लिए नया हूं और मुझे यह अच्छी तरह से पता नहीं है कि unique_ptrनिर्माणकर्ताओं या कार्यों में मापदंडों को कैसे संभालना है। स्वयं संदर्भित इस वर्ग पर विचार करें:

#include <memory>

class Base
{
  public:

    typedef unique_ptr<Base> UPtr;

    Base(){}
    Base(Base::UPtr n):next(std::move(n)){}

    virtual ~Base(){}

    void setNext(Base::UPtr n)
    {
      next = std::move(n);
    }

  protected :

    Base::UPtr next;

};

क्या यह है कि मुझे unique_ptrतर्कों को लेते हुए फ़ंक्शन कैसे लिखना चाहिए ?

और क्या मुझे std::moveकॉलिंग कोड में उपयोग करने की आवश्यकता है ?

Base::UPtr b1;
Base::UPtr b2(new Base());

b1->setNext(b2); //should I write b1->setNext(std::move(b2)); instead?


1
क्या यह एक विभाजन दोष नहीं है क्योंकि आप एक खाली पॉइंटर पर b1-> setNext कॉल कर रहे हैं?
बाल्की

जवाबों:


836

यहां एक अद्वितीय संकेतक को तर्क के रूप में लेने के संभावित तरीके हैं, साथ ही साथ उनके संबद्ध अर्थ भी हैं।

(ए) मूल्य द्वारा

Base(std::unique_ptr<Base> n)
  : next(std::move(n)) {}

उपयोगकर्ता को इसे कॉल करने के लिए, उन्हें निम्नलिखित में से एक करना होगा:

Base newBase(std::move(nextBase));
Base fromTemp(std::unique_ptr<Base>(new Base(...));

मूल्य द्वारा एक अद्वितीय सूचक लेने का अर्थ है कि आप प्रश्न में फ़ंक्शन / ऑब्जेक्ट / आदि के लिए सूचक के स्वामित्व को स्थानांतरित कर रहे हैं । newBaseनिर्माण के बाद , खालीnextBase होने की गारंटी है । आपके पास ऑब्जेक्ट नहीं है, और आपके पास अब इसका कोई संकेतक भी नहीं है। वह चला गया।

यह सुनिश्चित किया जाता है क्योंकि हम मान द्वारा पैरामीटर लेते हैं। std::moveवास्तव में कुछ भी स्थानांतरित नहीं करता है ; यह सिर्फ एक फैंसी कास्ट है। std::move(nextBase)एक रिटर्न Base&&कि करने के लिए एक आर-मूल्य संदर्भ है nextBase। बस इतना ही करता है।

क्योंकि Base::Base(std::unique_ptr<Base> n)r-value संदर्भ के बजाय इसके तर्क को मान लेता है, C ++ स्वचालित रूप से हमारे लिए एक अस्थायी निर्माण करेगा। यह std::unique_ptr<Base>इस Base&&बात से बनता है कि हमने फंक्शन किस माध्यम से दिया था std::move(nextBase)। यह इस अस्थायी का निर्माण है जो वास्तव में फ़ंक्शन तर्क से मूल्य को स्थानांतरितnextBase करता है n

(बी) गैर-कास्ट एल-मूल्य संदर्भ द्वारा

Base(std::unique_ptr<Base> &n)
  : next(std::move(n)) {}

इसे वास्तविक एल-मूल्य (एक नामित चर) पर बुलाया जाना है। इसे इस तरह अस्थायी नहीं कहा जा सकता है:

Base newBase(std::unique_ptr<Base>(new Base)); //Illegal in this case.

इसका अर्थ गैर-कॉन्स्टेबल संदर्भों के किसी अन्य उपयोग के अर्थ के समान है: फ़ंक्शन पॉइंटर के स्वामित्व का दावा कर सकता है या नहीं कर सकता है । इस कोड को दिया:

Base newBase(nextBase);

कोई गारंटी नहीं है कि nextBaseखाली है। यह खाली हो सकता है; यह नहीं हो सकता है। यह वास्तव में उस पर निर्भर करता है जो Base::Base(std::unique_ptr<Base> &n)करना चाहता है। इस वजह से, यह केवल उस कार्य हस्ताक्षर से बहुत स्पष्ट नहीं है जो होने जा रहा है; आपको कार्यान्वयन (या संबद्ध प्रलेखन) पढ़ना होगा।

उसके कारण, मैं इसे एक इंटरफ़ेस के रूप में नहीं सुझाऊँगा।

(सी) कास्ट एल-वैल्यू संदर्भ द्वारा

Base(std::unique_ptr<Base> const &n);

मैं एक कार्यान्वयन नहीं दिखाता, क्योंकि आप एक से स्थानांतरित नहीं कर सकतेconst& । A पास करके const&, आप कह रहे हैं कि फ़ंक्शन Baseपॉइंटर के माध्यम से एक्सेस कर सकता है , लेकिन यह इसे कहीं भी स्टोर नहीं कर सकता है । यह इसके स्वामित्व का दावा नहीं कर सकता।

यह उपयोगी हो सकता है। जरूरी नहीं कि अपने विशिष्ट मामले के लिए, लेकिन यह हमेशा अच्छा पता है हाथ किसी एक सूचक करने में सक्षम हो और वे उस करने के लिए नहीं कर सकते हैं (कोई दूर कास्टिंग की तरह, सी ++ के नियमों को तोड़ने के बिना constयह दावा स्वामित्व)। वे इसे स्टोर नहीं कर सकते। वे इसे दूसरों को दे सकते हैं, लेकिन उन अन्य लोगों को समान नियमों का पालन करना होगा।

(डी) आर-मूल्य संदर्भ द्वारा

Base(std::unique_ptr<Base> &&n)
  : next(std::move(n)) {}

यह "नॉन-कास्ट एल-वैल्यू संदर्भ" केस के समान है। अंतर दो चीजें हैं।

  1. आप एक अस्थायी पास कर सकते हैं:

    Base newBase(std::unique_ptr<Base>(new Base)); //legal now..
  2. गैर-अस्थायी तर्क पास करते समय आपको इसका उपयोग करना चाहिएstd::move

उत्तरार्द्ध वास्तव में समस्या है। यदि आप यह रेखा देखते हैं:

Base newBase(std::move(nextBase));

आपको एक उचित उम्मीद है कि, इस लाइन के पूरा होने के बाद, nextBaseखाली होना चाहिए। इसे वहां से स्थानांतरित किया जाना चाहिए था। आखिरकार, आपके पास std::moveवहां बैठे हैं, आपको बता रहे हैं कि आंदोलन हुआ है।

समस्या यह है कि यह नहीं है। यह गारंटी नहीं है कि इससे स्थानांतरित किया गया है। यह से स्थानांतरित किया गया हो सकता है, लेकिन आप केवल स्रोत कोड को देखकर ही जान पाएंगे। आप केवल फ़ंक्शन हस्ताक्षर से नहीं बता सकते।

अनुशंसाएँ

  • (ए) मूल्य द्वारा: यदि आप किसी कार्य के स्वामित्व के दावे के लिए हैं unique_ptr, तो इसे मूल्य के आधार पर लें।
  • (सी) कास्ट एल-वैल्यू संदर्भ द्वारा: यदि आप किसी फ़ंक्शन का मतलब केवल unique_ptrउस फ़ंक्शन के निष्पादन की अवधि के लिए उपयोग करते हैं , तो इसे लें const&। वैकल्पिक रूप से, एक पारित &या const&वास्तविक प्रकार के बजाय एक का उपयोग करने से, की ओर इशारा किया unique_ptr
  • (डी) आर-मूल्य संदर्भ द्वारा: यदि कोई फ़ंक्शन स्वामित्व (आंतरिक कोड पथों के आधार पर) का दावा कर सकता है या नहीं कर सकता है, तो इसे लें &&। लेकिन मैं जब भी संभव हो ऐसा करने के खिलाफ दृढ़ता से सलाह देता हूं।

कैसे अनूठे तरीके से छेड़छाड़ करें

आप कॉपी नहीं कर सकते unique_ptr। आप इसे केवल स्थानांतरित कर सकते हैं। ऐसा करने का उचित तरीका std::moveमानक पुस्तकालय फ़ंक्शन के साथ है।

यदि आप एक unique_ptrमूल्य लेते हैं , तो आप इसे स्वतंत्र रूप से स्थानांतरित कर सकते हैं। लेकिन वास्तव में आंदोलन नहीं होता है std::move। निम्नलिखित कथन लें:

std::unique_ptr<Base> newPtr(std::move(oldPtr));

यह वास्तव में दो कथन हैं:

std::unique_ptr<Base> &&temporary = std::move(oldPtr);
std::unique_ptr<Base> newPtr(temporary);

(नोट: उपरोक्त कोड तकनीकी रूप से संकलित नहीं करता है, क्योंकि गैर-अस्थायी आर-मूल्य संदर्भ वास्तव में आर-मूल्य नहीं हैं। यह केवल यहां डेमो उद्देश्यों के लिए है)।

यह temporaryकेवल एक r-value संदर्भ है oldPtr। यह आंदोलन के निर्माण में है newPtrजहां आंदोलन होता है। unique_ptrकंस्ट्रक्शन कंस्ट्रक्टर (एक कंस्ट्रक्टर जो &&खुद को लेता है) वास्तविक आंदोलन क्या है।

यदि आपके पास एक unique_ptrमूल्य है और आप इसे कहीं स्टोर करना चाहते हैं, तो आपको स्टोरेज करने के लिए उपयोग करना होगाstd::move


5
@ नाइकोल: लेकिन std::moveइसकी वापसी का नाम नहीं है। याद रखें कि नामित रूवल संदर्भ अंतराल हैं। ideone.com/VlEM3
आर। मार्टिनो फर्नांडीस

31
मैं इस जवाब से आधारभूत रूप से सहमत हूं, लेकिन कुछ टिप्पणी की है। (1) मुझे नहीं लगता कि कॉन्स्टल लवल्यू के संदर्भ के लिए एक वैध उपयोग का मामला है: सब कुछ कैली उस के साथ कर सकता था, यह कॉन्सट (नंगे) पॉइंटर के संदर्भ में भी कर सकता है, या इससे भी बेहतर पॉइंटर खुद [ स्वामित्व जानना कोई व्यवसाय नहीं है unique_ptr; हो सकता है कि कुछ अन्य कॉलर्स को समान कार्यक्षमता की आवश्यकता हो, लेकिन shared_ptrइसके बजाय होल्ड कर रहे हों ] (2) कॉल लैवल्यू रेफरेंस उपयोगी हो सकता है यदि फ़ंक्शन पॉइंटर को संशोधित करता है, उदाहरण के लिए, लिंक की गई सूची से (सूची के स्वामित्व वाले) नोड्स को जोड़ना या हटाना।
मार्क वैन लीउवेन

8
... (३) यद्यपि आपका तर्क मूल्य संदर्भ से गुजरने के पक्षधर होने के पक्षधर है, यह समझ में आता है, मुझे लगता है कि मानक हमेशा ही unique_ptrमानों को संदर्भ (उदाहरण के लिए जब उन्हें रूपांतरित करते हुए shared_ptr) से गुजरता है । इसके लिए तर्क यह हो सकता है कि यह थोड़ा अधिक कुशल है (अस्थायी संकेत करने के लिए कोई कदम नहीं किया जाता है), जबकि यह फोन करने वाले को सटीक समान अधिकार देता है (हो सकता है कि इसमें दरारें, या अंतराल में लिपटे हुए हों std::move, लेकिन नग्न अंतराल नहीं हैं)।
मार्क वैन लीउवेन

19
मार्क ने जो कहा, उसे दोहराने के लिए और सटर के हवाले से कहा : "एक कांस्ट यूनिक_प्रटर और एक पैरामीटर के रूप में उपयोग न करें, विजेट का उपयोग करें *"
जॉन

17
हमने उप-मान के साथ एक समस्या की खोज की है - यह कदम तर्क आरंभीकरण के दौरान होता है, जो अन्य तर्क मूल्यांकन (निश्चित रूप से एक initializer_list में छोड़कर) के संबंध में अनियंत्रित है। जबकि एक प्रतिद्वंद्विता संदर्भ को स्वीकार करने से फ़ंक्शन कॉल के बाद होने वाले कदम का जोरदार आदेश होता है, और इसलिए अन्य तर्कों के मूल्यांकन के बाद। इसलिए जब भी स्वामित्व लिया जाएगा, तब संदर्भ के संदर्भ को स्वीकार करना चाहिए।
बेन वोइगट

57

मुझे उन ऑब्जेक्ट्स के आस-पास पासिंग पॉइंट्स के विभिन्न व्यवहार्य मोड्स को बताने की कोशिश करें, जिनकी मेमोरी को std::unique_ptrक्लास टेम्पलेट के एक उदाहरण द्वारा प्रबंधित किया जाता है ; यह पुराने std::auto_ptrवर्ग के टेम्पलेट पर भी लागू होता है (जो मुझे लगता है कि सभी यूनिक पॉइंटर का उपयोग करने की अनुमति देता है, लेकिन इसके लिए संशोधित रूपांतरों को स्वीकार किया जाएगा, जहां प्रतिद्वंद्वियों की अपेक्षा की जाती है, बिना आह्वान किए std::move), और कुछ हद तक भी std::shared_ptr

चर्चा के लिए एक ठोस उदाहरण के रूप में मैं निम्नलिखित सरल सूची प्रकार पर विचार करूंगा

struct node;
typedef std::unique_ptr<node> list;
struct node { int entry; list next; }

ऐसी सूची के उदाहरण (जिन्हें अन्य उदाहरणों के साथ भागों को साझा करने या परिपत्र होने की अनुमति नहीं दी जा सकती है) पूरी तरह से किसी के पास है जो प्रारंभिक listसंकेतक रखता है । यदि क्लाइंट कोड जानता है कि यह जो सूची संग्रहीत करता है, वह कभी खाली नहीं होगी, तो यह nodeसीधे a के बजाय पहले स्टोर करना चुन सकता है listnodeपरिभाषित किए जाने की आवश्यकता के लिए कोई विध्वंसक नहीं है : चूंकि इसके क्षेत्रों के लिए विनाशकों को स्वचालित रूप से कहा जाता है, इसलिए पूरी सूची को प्रारंभिक सूचक या नोड के जीवनकाल समाप्त होने पर स्मार्ट पॉइंटर विध्वंसक द्वारा पुन: हटा दिया जाएगा।

यह पुनरावर्ती प्रकार कुछ मामलों पर चर्चा करने का अवसर देता है जो स्मार्ट सूचक के सादे डेटा के मामले में कम दिखाई देते हैं। साथ ही फ़ंक्शन स्वयं कभी-कभी क्लाइंट कोड का एक उदाहरण (पुनरावर्ती) प्रदान करते हैं। इसके लिए टाइप किए गए listपाठ्यक्रम की ओर से पक्षपाती है unique_ptr, लेकिन परिभाषा का उपयोग करने के लिए auto_ptrया shared_ptrइसके बजाय बदलने के लिए बदला जा सकता है, जो नीचे कहा गया है (जो कि विध्वंसकों को लिखने की आवश्यकता के बिना अपवाद सुरक्षा के बारे में निश्चित रूप से आश्वस्त किए बिना बदले जाने की आवश्यकता है)।

चारों ओर स्मार्ट पॉइंटर्स पास करने के मोड

मोड 0: स्मार्ट पॉइंटर के बजाय एक पॉइंटर या संदर्भ तर्क पास करें

यदि आपका कार्य स्वामित्व से संबंधित नहीं है, तो यह पसंदीदा तरीका है: इसे बिल्कुल स्मार्ट पॉइंटर न बनाएं। इस मामले में, आपके फ़ंक्शन को यह चिंता करने की आवश्यकता नहीं है कि इंगित की गई वस्तु का मालिक कौन है, या इसका क्या मतलब है कि स्वामित्व प्रबंधित है, इसलिए एक कच्चा सूचक पास करना पूरी तरह से सुरक्षित है, और सबसे लचीला रूप है, क्योंकि मालिक की परवाह किए बिना हमेशा ग्राहक हो सकता है एक कच्चे सूचक का उत्पादन करें (या तो getविधि को बुलाकर या ऑपरेटर के पते से &)।

उदाहरण के लिए, इस तरह की सूची की लंबाई की गणना करने के लिए, एक listतर्क नहीं दिया जाना चाहिए , लेकिन एक कच्चा सूचक:

size_t length(const node* p)
{ size_t l=0; for ( ; p!=nullptr; p=p->next.get()) ++l; return l; }

एक क्लाइंट जो एक वैरिएबल रखता है , list headइस फ़ंक्शन को कॉल कर सकता है length(head.get()), जबकि एक क्लाइंट जो node nएक गैर-खाली सूची का प्रतिनिधित्व करने के बजाय चुन सकता है, कॉल कर सकता है length(&n)

यदि पॉइंटर को नॉन नल होने की गारंटी दी जाती है (जो कि लिस्ट खाली होने के बाद भी यहां नहीं है) तो कोई पॉइंटर के बजाय रेफरेंस पास करना पसंद कर सकता है। यह गैर-के लिए एक पॉइंटर / संदर्भ हो सकता है - constयदि फ़ंक्शन को नोड (एस) की सामग्री को अपडेट करने की आवश्यकता है, तो उनमें से किसी को जोड़ने या हटाने के बिना (बाद में स्वामित्व शामिल होगा)।

एक दिलचस्प मामला जो मोड 0 श्रेणी में आता है वह सूची की एक (गहरी) प्रतिलिपि बना रहा है; हालांकि ऐसा करने वाले किसी फ़ंक्शन को उस प्रतिलिपि के स्वामित्व के स्वामित्व को हस्तांतरित करना चाहिए, जो उस प्रतिलिपि के सूची के स्वामित्व से संबंधित नहीं है। तो इसे निम्नानुसार परिभाषित किया जा सकता है:

list copy(const node* p)
{ return list( p==nullptr ? nullptr : new node{p->entry,copy(p->next.get())} ); }

इस कोड को योग्यता के आधार एक करीबी नजर, दोनों कारण है कि यह सब पर (पुनरावर्ती कॉल के परिणाम के संकलित करने के लिए के रूप में प्रश्न के लिए copyinitialiser सूची बांध में की चाल निर्माता में rvalue संदर्भ तर्क के लिए unique_ptr<node>, उर्फ list, जब initialising nextके क्षेत्र उत्पन्न node), और इस सवाल के लिए कि यह अपवाद-सुरक्षित क्यों है (यदि पुनरावर्ती आवंटन प्रक्रिया के दौरान मेमोरी समाप्त हो जाती है और कुछ कॉल newफेंकता है std::bad_alloc, तो उस समय आंशिक रूप से निर्मित सूची के लिए एक सूचक को अस्थायी रूप से गुमनाम रूप से रखा जाता है listप्रारंभिक सूची के लिए बनाया गया है, और इसका विध्वंसक उस आंशिक सूची को साफ करेगा)। वैसे एक को बदलने के लिए प्रलोभन का विरोध करना चाहिए (जैसा कि मैंने शुरू में किया था) दूसरा nullptrद्वाराp, जो सब के बाद उस बिंदु पर शून्य होने के लिए जाना जाता है: कोई भी एक कच्चे (कच्चे) पॉइंटर से स्मार्ट पॉइंटर का निर्माण नहीं कर सकता है , यहां तक ​​कि जब यह अशक्त होने के लिए जाना जाता है।

मोड 1: मूल्य से एक स्मार्ट पॉइंटर पास करें

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

ऐसे मामलों के लिए, जहां कहा जाने वाला फ़ंक्शन बिना शर्त के (पायलटों) पॉइंट-टू-ऑब्जेक्ट का स्वामित्व लेता है, इस मोड का उपयोग std::unique_ptrया std::auto_ptrइसके स्वामित्व के साथ एक पॉइंटर पास करने का एक अच्छा तरीका है, जो मेमोरी लीक के किसी भी जोखिम से बचा जाता है। फिर भी मुझे लगता है कि ऐसी बहुत कम स्थितियाँ हैं जहाँ मोड 3 नीचे पसंद नहीं किया जाना है (कभी-कभार थोड़ा) 1 मोड पर। इस कारण से मैं इस मोड का कोई उपयोग उदाहरण प्रदान नहीं करूँगा। (लेकिन reversedनीचे दिए गए मोड 3 का उदाहरण देखें, जहां यह टिप्पणी की गई है कि मोड 1 कम से कम उतना ही अच्छा करेगा।) यदि फ़ंक्शन केवल इस सूचक की तुलना में अधिक तर्क लेता है, तो ऐसा हो सकता है कि मोड से बचने के लिए तकनीकी कारण के अतिरिक्त है। 1 (साथ std::unique_ptrया std::auto_ptr): चूंकि पॉइंटर वैरिएबल पास करते समय एक वास्तविक चाल ऑपरेशन होता हैpअभिव्यक्ति द्वारा std::move(p), यह नहीं माना जा सकता है कि pअन्य तर्कों (मूल्यांकन के क्रम अनिर्दिष्ट) का मूल्यांकन करते समय एक उपयोगी मूल्य रखता है, जिससे सूक्ष्म त्रुटियां हो सकती हैं; इसके विपरीत, मोड 3 का उपयोग यह आश्वासन देता है कि pफ़ंक्शन कॉल से पहले कोई भी कदम नहीं उठाता है, इसलिए अन्य तर्क सुरक्षित रूप से एक मूल्य तक पहुंच सकते हैं p

जब इसका उपयोग किया जाता है std::shared_ptr, तो यह मोड दिलचस्प होता है कि एकल फ़ंक्शन परिभाषा के साथ यह कॉलर को यह चुनने की अनुमति देता है कि फ़ंक्शन द्वारा उपयोग की जाने वाली एक नई साझाकरण प्रतिलिपि बनाते समय सूचक की एक साझा प्रतिलिपि अपने लिए रखनी है या नहीं (यह तब होता है जब एक लेवल्यू होता है तर्क प्रदान किया जाता है; कॉल पर उपयोग किए जाने वाले साझा पॉइंटर्स के लिए कॉपी कंस्ट्रक्टर संदर्भ संख्या को बढ़ाता है), या फ़ंक्शन को केवल एक को बनाए रखने या संदर्भ गणना को छूने के बिना सूचक की एक प्रति देने के लिए (यह तब होता है जब एक तर्क तर्क प्रदान किया जाता है, संभवतः एक आह्वान std::move) उदाहरण के लिए

void f(std::shared_ptr<X> x) // call by shared cash
{ container.insert(std::move(x)); } // store shared pointer in container

void client()
{ std::shared_ptr<X> p = std::make_shared<X>(args);
  f(p); // lvalue argument; store pointer in container but keep a copy
  f(std::make_shared<X>(args)); // prvalue argument; fresh pointer is just stored away
  f(std::move(p)); // xvalue argument; p is transferred to container and left null
}

समान को अलग-अलग परिभाषित करने void f(const std::shared_ptr<X>& x)(लैवल्यू केस के लिए) और void f(std::shared_ptr<X>&& x)( रेवल्यू केस के लिए) द्वारा प्राप्त किया जा सकता है , जिसमें फ़ंक्शन बॉडीज केवल उस में भिन्न होती हैं, पहला संस्करण कॉपी सिमेंटिक्स का उपयोग करता है (उपयोग करते समय कॉपी निर्माण / असाइनमेंट का उपयोग करता है x), लेकिन दूसरा संस्करण शब्दार्थ ( std::move(x)इसके बजाय, उदाहरण कोड में लेखन )। इसलिए साझा किए गए बिंदुओं के लिए, कुछ कोड दोहराव से बचने के लिए मोड 1 उपयोगी हो सकता है।

मोड 2: (सूचक) लैवल्यू संदर्भ द्वारा एक स्मार्ट पॉइंटर पास करें

यहां फ़ंक्शन को केवल स्मार्ट पॉइंटर के लिए एक परिवर्तनीय संदर्भ होने की आवश्यकता होती है, लेकिन यह इसके साथ क्या करेगा इसका कोई संकेत नहीं देता है। मैं कार्ड द्वारा इस पद्धति को कॉल करना चाहूंगा : कॉलर क्रेडिट कार्ड नंबर देकर भुगतान सुनिश्चित करता है। संदर्भ का उपयोग पॉइंट-टू-ऑब्जेक्ट के स्वामित्व को लेने के लिए किया जा सकता है, लेकिन इसके पास नहीं है। इस मोड में एक संशोधित अंतराल तर्क प्रदान करने की आवश्यकता होती है, इस तथ्य के अनुसार कि फ़ंक्शन के वांछित प्रभाव में तर्क चर में एक उपयोगी मूल्य छोड़ना शामिल हो सकता है। एक कॉल राइवल अभिव्यक्ति के साथ जो इस तरह के फ़ंक्शन को पास करना चाहता है, उसे कॉल करने में सक्षम होने के लिए नामांकित चर में संग्रहीत करने के लिए मजबूर किया जाएगा, क्योंकि भाषा केवल स्थिरांक को अंतर्निहित रूपांतरण प्रदान करती हैलवल्यू संदर्भ (एक अस्थायी का संदर्भ देते हुए) एक लय से। (विपरीत स्थिति से संभाला के विपरीत std::move, से एक डाली Y&&को Y&, साथ Yस्मार्ट सूचक प्रकार, संभव नहीं है; फिर भी इस रूपांतरण एक साधारण टेम्पलेट समारोह यदि वास्तव में वांछित द्वारा प्राप्त किया जा सकता है, को देखने के https://stackoverflow.com/a/24868376 / 1436796 )। उस मामले के लिए जहां कहा जाता है कि फ़ंक्शन बिना शर्त के स्वामित्व का इरादा रखता है, तर्क से चोरी करते हुए, एक तर्क तर्क प्रदान करने का दायित्व गलत संकेत दे रहा है: कॉल के बाद चर का कोई उपयोगी मूल्य नहीं होगा। इसलिए मोड 3, जो हमारे फ़ंक्शन के अंदर समान संभावनाएं देता है, लेकिन कॉल करने वालों को एक प्रतिद्वंद्विता प्रदान करने के लिए कहता है, इस तरह के उपयोग के लिए प्राथमिकता दी जानी चाहिए।

हालाँकि मोड 2 के लिए एक वैध उपयोग मामला है, अर्थात् फ़ंक्शंस जो सूचक को संशोधित कर सकता है , या उस ऑब्जेक्ट को इंगित किया जा सकता है जिसमें स्वामित्व शामिल है । उदाहरण के लिए, एक फ़ंक्शन जो नोड को उपसर्ग listकरता है, ऐसे उपयोग का एक उदाहरण प्रदान करता है:

void prepend (int x, list& l) { l = list( new node{ x, std::move(l)} ); }

स्पष्ट रूप से कॉलर्स को उपयोग करने के लिए मजबूर करना यहां अवांछनीय होगा std::move, क्योंकि उनके स्मार्ट पॉइंटर अभी भी कॉल के बाद एक अच्छी तरह से परिभाषित और गैर-खाली सूची के मालिक हैं, हालांकि पहले की तुलना में एक अलग है।

फिर से यह देखना दिलचस्प है कि prependमुफ्त मेमोरी की कमी के लिए कॉल विफल होने पर क्या होता है । फिर newकॉल फेंक देगा std::bad_alloc; इस समय पर, चूंकि कोई nodeभी आवंटित नहीं किया जा सकता है, यह निश्चित है कि पास किए गए रेवल्यू रेफरेंस (मोड 3) को std::move(l)अभी तक पायलट नहीं किया जा सकता है, क्योंकि आवंटित किए जाने वाले nextक्षेत्र का निर्माण nodeकरने के लिए किया जाएगा। इसलिए मूल स्मार्ट पॉइंटर lअभी भी मूल सूची रखता है जब त्रुटि डाली जाती है; उस सूची को या तो स्मार्ट पॉइंटर डिस्ट्रक्टर द्वारा ठीक से नष्ट कर दिया जाएगा, या मामले में lपर्याप्त रूप से शुरुआती catchक्लॉज के लिए धन्यवाद से बच जाना चाहिए , यह अभी भी मूल सूची को रखेगा।

यह एक रचनात्मक उदाहरण था; इस प्रश्न पर एक पलक के साथ कोई भी दिए गए मान वाले पहले नोड को हटाने का अधिक विनाशकारी उदाहरण दे सकता है, यदि कोई हो:

void remove_first(int x, list& l)
{ list* p = &l;
  while ((*p).get()!=nullptr and (*p)->entry!=x)
    p = &(*p)->next;
  if ((*p).get()!=nullptr)
    (*p).reset((*p)->next.release()); // or equivalent: *p = std::move((*p)->next); 
}

फिर से शुद्धता यहाँ काफी सूक्ष्म है। विशेष रूप से, अंतिम कथन में, (*p)->nextहटाए जाने के लिए नोड के अंदर रखे गए पॉइंटर को अनलिंक किया जाता है (द्वारा release, जो पॉइंटर लौटाता है, लेकिन मूल अशक्त बनाता है) इससे पहले reset कि उस नोड को नष्ट कर देता है (स्पष्ट रूप से) पुराने नोड को नष्ट कर देता है p), यह सुनिश्चित करता है एक और केवल एक नोड उस समय नष्ट हो जाता है। (टिप्पणी में उल्लिखित वैकल्पिक रूप में, इस समय को std::unique_ptrउदाहरण के चाल-असाइनमेंट के कार्यान्वयन के आंतरिक listबिंदुओं पर छोड़ दिया जाएगा , मानक 20.7.1.2.3 कहता है; 2 कि यह ऑपरेटर "के रूप में मानो" कॉलिंग reset(u.release())", जहां समय भी यहां सुरक्षित होना चाहिए।"

ध्यान दें कि prependऔर remove_firstउन ग्राहकों द्वारा नहीं बुलाया जा सकता है जो nodeहमेशा एक गैर-रिक्त सूची के लिए एक स्थानीय चर संग्रहित करते हैं , और ठीक ही इसलिए कि दिए गए कार्यान्वयन ऐसे मामलों के लिए काम नहीं कर सके।

मोड 3: एक स्मार्ट पॉइंटर को पास करें (मोडिफायेबल) रेवल्यू रेफरेंस द्वारा

यह केवल सूचक के स्वामित्व को लेते समय उपयोग करने के लिए पसंदीदा मोड है। मैं चेक द्वारा इस विधि को कॉल करना चाहूंगा : कॉल करने वाले को स्वामित्व को त्यागना चाहिए, जैसे कि चेक प्रदान करके, नकद पर हस्ताक्षर करके, लेकिन वास्तविक निकासी तब तक के लिए स्थगित कर दी जाती है जब तक कि फ़ंक्शन वास्तव में पॉइंटर को स्थानांतरित नहीं करता है (ठीक वैसे ही जब यह मोड 2 होगा )। "चेक पर हस्ताक्षर" का मतलब है कि कॉल करने वालों को तर्क में लपेटना पड़ता है std::move(जैसे कि मोड 1 में) यदि यह एक अंतराल है (यदि यह एक प्रतिद्वंद्विता है, तो "स्वामित्व छोड़ देना" हिस्सा स्पष्ट है और इसके लिए अलग कोड की आवश्यकता नहीं है)।

ध्यान दें कि तकनीकी रूप से मोड 3 बिल्कुल मोड 2 के रूप में व्यवहार करता है, इसलिए तथाकथित फ़ंक्शन को स्वामित्व मानने की आवश्यकता नहीं है ; हालाँकि मैं इस बात पर ज़ोर देना चाहूंगा कि यदि स्वामित्व हस्तांतरण (सामान्य उपयोग में) के बारे में कोई अनिश्चितता है, तो मोड 2 को मोड 3 के लिए प्राथमिकता दी जानी चाहिए, ताकि मोड 3 का उपयोग करना फोन करने वालों के लिए एक संकेत है कि वे स्वामित्व दे रहे हैं । कोई यह प्रतिशोध कर सकता है कि केवल मोड 1 तर्क वास्तव में संकेतों को कॉल करने वालों को स्वामित्व के नुकसान को मजबूर करता है। लेकिन अगर किसी ग्राहक को कॉल किए गए फ़ंक्शन के इरादों के बारे में कोई संदेह है, तो उसे फ़ंक्शन के विनिर्देशों को जानना चाहिए, जिसे उसके संदेह को दूर करना चाहिए।

हमारे listप्रकार को शामिल करने वाले एक विशिष्ट उदाहरण को खोजना आश्चर्यजनक रूप से कठिन है जो मोड 3 तर्क पासिंग का उपयोग करता है। एक सूची bको दूसरी सूची के अंत में ले aजाना एक विशिष्ट उदाहरण है; हालांकि a(जो बच जाता है और ऑपरेशन का परिणाम रखता है) मोड 2 का उपयोग करके बेहतर तरीके से पारित हो जाता है:

void append (list& a, list&& b)
{ list* p=&a;
  while ((*p).get()!=nullptr) // find end of list a
    p=&(*p)->next;
  *p = std::move(b); // attach b; the variable b relinquishes ownership here
}

मोड 3 तर्क पासिंग का एक शुद्ध उदाहरण निम्नलिखित है जो एक सूची (और उसके स्वामित्व) को लेता है, और रिवर्स क्रम में समान नोड्स वाली सूची देता है।

list reversed (list&& l) noexcept // pilfering reversal of list
{ list p(l.release()); // move list into temporary for traversal
  list result(nullptr);
  while (p.get()!=nullptr)
  { // permute: result --> p->next --> p --> (cycle to result)
    result.swap(p->next);
    result.swap(p);
  }
  return result;
}

इस फ़ंक्शन l = reversed(std::move(l));को सूची को स्वयं में उलटने के लिए कहा जा सकता है, लेकिन उलट सूची का उपयोग अलग-अलग तरीके से भी किया जा सकता है।

यहां तर्क को तुरंत दक्षता के लिए एक स्थानीय चर में ले जाया जाता है (एक व्यक्ति पैरामीटर को lसीधे जगह में इस्तेमाल कर सकता है p, लेकिन फिर हर बार इसे एक्सेस करने से एक अतिरिक्त स्तर अप्रत्यक्ष हो जाएगा); इसलिए मोड 1 तर्क पासिंग के साथ अंतर न्यूनतम है। वास्तव में उस मोड का उपयोग करते हुए, तर्क सीधे स्थानीय चर के रूप में कार्य कर सकता था, इस प्रकार उस प्रारंभिक कदम से बचा जा सकता है; यह सामान्य सिद्धांत का एक उदाहरण है कि यदि संदर्भ द्वारा पारित एक तर्क केवल एक स्थानीय चर को आरंभ करने के लिए कार्य करता है, तो कोई इसे मान के बजाय केवल इसे पारित कर सकता है और स्थानीय चर के रूप में पैरामीटर का उपयोग कर सकता है।

मोड 3 का उपयोग करना मानक द्वारा वकालत करना प्रतीत होता है, जैसा कि इस तथ्य से गवाह है कि सभी प्रदान किए गए पुस्तकालय कार्य जो मोड 3 का उपयोग करके स्मार्ट पॉइंटर्स के स्वामित्व को स्थानांतरित करते हैं। बिंदु में एक विशेष रूप से ठोस मामला कंस्ट्रक्टर है std::shared_ptr<T>(auto_ptr<T>&& p)। उस कंस्ट्रक्टर का उपयोग std::tr1एक संशोधित लैवल्यू रेफरेंस ( auto_ptr<T>&कॉपी कंस्ट्रक्टर की तरह ) लेने के लिए किया जाता है, और इसलिए इसे एक auto_ptr<T>लैवल्यू के pरूप में कहा जा सकता है std::shared_ptr<T> q(p), जिसके बाद pइसे शून्य पर रीसेट कर दिया गया है। तर्क पास होने के मोड 2 से 3 में परिवर्तन के कारण, इस पुराने कोड को अब फिर से लिखना std::shared_ptr<T> q(std::move(p))होगा और फिर काम करना जारी रखना होगा। मैं समझता हूं कि समिति को यहां मोड 2 पसंद नहीं था, लेकिन उनके पास मोड 1 में बदलने का विकल्प था, परिभाषित करकेstd::shared_ptr<T>(auto_ptr<T> p)इसके बजाय, वे यह सुनिश्चित कर सकते थे कि पुराना कोड संशोधन के बिना काम करता है, क्योंकि (अद्वितीय-पॉइंटर्स के विपरीत) ऑटो-पॉइंटर्स को चुपचाप एक मूल्य के लिए निष्क्रिय किया जा सकता है (सूचक ऑब्जेक्ट खुद को प्रक्रिया में शून्य होने के लिए रीसेट किया जा रहा है)। जाहिरा तौर पर समिति ने मोड 1 से अधिक मोड 3 की वकालत को प्राथमिकता दी, ताकि वे पहले से ही उपयोग किए गए उपयोग के लिए भी मोड 1 का उपयोग करने के बजाय मौजूदा कोड को सक्रिय रूप से तोड़ सकें ।

जब मोड 3 से अधिक मोड 1 पसंद करना है

मोड 1 कई मामलों में पूरी तरह से प्रयोग करने योग्य है, और उन मामलों में मोड 3 से अधिक पसंद किया जा सकता है जहां स्वामित्व ग्रहण करना अन्यथा स्मार्ट पॉइंटर को स्थानीय चर में ले जाने का रूप लेता है जैसा कि reversedऊपर दिए गए उदाहरण में है। हालाँकि, मैं अधिक सामान्य मामले में मोड 3 को प्राथमिकता देने के दो कारण देख सकता हूं:

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

  • क्या यह बोधगम्य होना चाहिए कि फ़ंक्शन कॉल की शुरुआत और उस बिंदु के बीच कुछ भी फेंकता है, जहां यह (या कुछ निहित कॉल) वास्तव में एक अन्य डेटा संरचना में इंगित ऑब्जेक्ट को स्थानांतरित करता है (और यह अपवाद फ़ंक्शन के अंदर पहले से ही पकड़ा नहीं गया है) ), फिर मोड 1 का उपयोग करते समय, स्मार्ट पॉइंटर द्वारा संदर्भित ऑब्जेक्ट को catchखंड को अपवाद से पहले नष्ट कर दिया जाएगा (क्योंकि फ़ंक्शन पैरामीटर स्टैक अनइंडिंग के दौरान नष्ट हो गया था), लेकिन ऐसा नहीं है जब मोड 3 का उपयोग कर। बाद वाला देता है। कॉलर के पास ऐसे मामलों में ऑब्जेक्ट के डेटा को पुनर्प्राप्त करने का विकल्प होता है (अपवाद को पकड़कर)। ध्यान दें कि यहां मोड 1 स्मृति रिसाव का कारण नहीं बनता है , लेकिन इससे प्रोग्राम के लिए डेटा की अपरिवर्तनीय हानि हो सकती है, जो अवांछनीय भी हो सकती है।

एक स्मार्ट सूचक लौटना: हमेशा मूल्य से

एक स्मार्ट पॉइंटर को वापस करने के बारे में एक शब्द का निष्कर्ष निकालने के लिए , संभवतः कॉल करने वाले द्वारा उपयोग किए गए ऑब्जेक्ट के लिए इंगित किया गया है। यह वास्तव में कार्यों में गुजरने वाले बिंदुओं के साथ तुलनीय मामला नहीं है, लेकिन पूर्णता के लिए मैं इस बात पर जोर देना चाहूंगा कि ऐसे मामलों में हमेशा मूल्य से वापसी करें (और बयान में उपयोग न करें )। कोई भी एक पॉइंटर का संदर्भ प्राप्त नहीं करना चाहता है जो शायद सिर्फ निक्स किया गया है।std::movereturn


1
मोड 0 के लिए +1 - अनूठे_ptr के बजाय अंतर्निहित पॉइंटर पास करना। थोड़ा सा विषय (चूंकि सवाल एक यूनिक_प्रेट पास करने के बारे में है) लेकिन यह सरल है और समस्याओं से बचा जाता है।
मटका जूल

" मोड 1 यहां मेमोरी रिसाव का कारण नहीं बनता है " - इसका मतलब है कि मोड 3 मेमोरी मेमोरी लीक का कारण बनता है, जो सच नहीं है। भले ही unique_ptrइसे स्थानांतरित कर दिया गया हो या नहीं, यह तब भी मूल्य को अच्छी तरह से हटा देगा यदि यह अभी भी इसे नष्ट या फिर से इस्तेमाल किया जाता है।
rustyx

@RustyX: मैं यह नहीं देख सकता कि आप उस निहितार्थ को कैसे कसते हैं, और मैंने कभी भी यह कहने का इरादा नहीं किया कि आपको क्या लगता है कि मैं निहित हूं। मेरा मतलब है कि कहीं और के रूप में unique_ptrएक स्मृति रिसाव को रोकता है (और इस तरह से एक अनुबंध को पूरा करता है), लेकिन यहाँ (यानी, मोड 1 का उपयोग करके) यह (विशिष्ट परिस्थितियों में) कुछ ऐसा हो सकता है जिसे और भी अधिक हानिकारक माना जा सकता है। , अर्थात् डेटा का नुकसान (इंगित किए गए मूल्य का विनाश) जिसे मोड 3 का उपयोग करने से बचा जा सकता था
मार्क वैन लीउवेन

4

हां, यदि आपको unique_ptrकंस्ट्रक्टर में बाय वैल्यू लेना है । स्पष्टता एक अच्छी बात है। चूँकि unique_ptrअस्वाभाविक है (निजी प्रतिलिपि ctor), जो आपने लिखा है वह आपको एक संकलक त्रुटि देनी चाहिए।


3

संपादित करें: यह उत्तर गलत है, भले ही, कड़ाई से बोल रहा हो, कोड काम करता है। मैं केवल इसे यहां छोड़ रहा हूं क्योंकि इसके तहत चर्चा बहुत उपयोगी है। यह अन्य उत्तर मेरे द्वारा पिछली बार संपादित किए गए समय का सबसे अच्छा उत्तर है: मैं एक रचनाकार या एक फ़ंक्शन के लिए एक यूनिक_प्रेट तर्क कैसे पास करूं?

इसका मूल विचार ::std::moveयह है कि जो लोग आपके पास से गुजर रहे हैं, unique_ptrउन्हें इसका उपयोग इस ज्ञान को व्यक्त करने के लिए करना चाहिए कि वे जानते हैं कि वे जिस unique_ptrमार्ग से गुजर रहे हैं वह स्वामित्व खो देगा।

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

इसका मतलब है कि आपके दो तरीकों को इस तरह दिखना चाहिए:

Base(Base::UPtr &&n) : next(::std::move(n)) {} // Spaces for readability

void setNext(Base::UPtr &&n) { next = ::std::move(n); }

तब विधियों का उपयोग करने वाले लोग ऐसा करेंगे:

Base::UPtr objptr{ new Base; }
Base::UPtr objptr2{ new Base; }
Base fred(::std::move(objptr)); // objptr now loses ownership
fred.setNext(::std::move(objptr2)); // objptr2 now loses ownership

जैसा कि आप देखते हैं, ::std::moveइंगित करता है कि सूचक उस बिंदु पर स्वामित्व खोने जा रहा है जहां यह जानना सबसे अधिक प्रासंगिक और उपयोगी है। यदि यह अदृश्य रूप से होता है, तो आपके वर्ग का उपयोग करने वाले लोगों के लिए यह बहुत भ्रामक होगा कि objptrअचानक स्पष्ट रूप से बिना किसी स्पष्ट कारण के स्वामित्व खो दिया जाए।


2
नामित रूवल्यू संदर्भ लैवल हैं।
आर। मार्टिनो फर्नांडीस

क्या आपको यकीन है कि यह Base fred(::std::move(objptr));नहीं है Base::UPtr fred(::std::move(objptr));?
कोडबैंक 1

1
मेरी पिछली टिप्पणी में जोड़ने के लिए: यह कोड संकलित नहीं करेगा। आपको अभी भी std::moveनिर्माता और विधि दोनों के कार्यान्वयन में उपयोग करने की आवश्यकता है । और यहां तक ​​कि जब आप मूल्य से गुजरते हैं, तो कॉल करने वाले को अभी भी std::movelvalues ​​पास करने के लिए उपयोग करना चाहिए । मुख्य अंतर यह है कि इंटरफ़ेस को स्पष्ट करने वाले पास-दर-मूल्य के साथ यह खो जाएगा। देखें निकोल बोलस एक अन्य उत्तर पर टिप्पणी करें।
आर। मार्टिनो फर्नांडीस

@ कोडबेलंक 1: हाँ। मैं प्रदर्शन कर रहा हूं कि कैसे आधार में कंस्ट्रक्टर और विधियों का उपयोग किया जाए जो कि रेवल्यू रेफरेंस लेते हैं।
सर्वव्यापी

@ R.MartinhoFernandes: ओह, दिलचस्प है। मुझे लगता है कि समझ में आता है। मैं आपसे गलत होने की उम्मीद कर रहा था, लेकिन वास्तविक परीक्षण ने आपको सही साबित कर दिया। अब तय हो गया।
सर्वव्यापी

0
Base(Base::UPtr n):next(std::move(n)) {}

जितना बेहतर होना चाहिए

Base(Base::UPtr&& n):next(std::forward<Base::UPtr>(n)) {}

तथा

void setNext(Base::UPtr n)

होना चाहिए

void setNext(Base::UPtr&& n)

एक ही शरीर के साथ

और ... क्या है evtमें handle()??


3
वहाँ का उपयोग करने में कोई लाभ है std::forward: यहां Base::UPtr&&है हमेशा एक rvalue संदर्भ प्रकार, और std::moveएक rvalue के रूप में यह गुजरता है। यह पहले से ही सही ढंग से अग्रेषित है।
आर। मार्टिनो फर्नांडीस

7
मैं दृढ़ता से असहमत हूँ। यदि कोई फ़ंक्शन एक unique_ptrमान लेता है , तो आपको गारंटी दी जाती है कि नए मूल्य पर एक चाल निर्माणकर्ता को बुलाया गया था (या बस यह कि आपको एक अस्थायी दिया गया था)। यह सुनिश्चित करता है कि unique_ptrउपयोगकर्ता के पास अब जो चर है वह खाली है । यदि आप &&इसके बजाय इसे लेते हैं , तो यह केवल तभी खाली हो जाएगा जब आपका कोड एक चाल कार्रवाई को लागू करता है। आपका तरीका, वैरिएबल के लिए यह संभव है कि उपयोगकर्ता को वहां से नहीं जाना है। जो उपयोगकर्ता के std::moveसंदिग्ध और भ्रमित करने का उपयोग करता है। का उपयोग std::moveहमेशा यह सुनिश्चित करना चाहिए कि कुछ ले जाया गया था ।
निकोल बोलस

@ नाइकोलोलस: आप सही कह रहे हैं। मैं अपना उत्तर हटा दूंगा क्योंकि यह काम करते समय, आपका अवलोकन बिल्कुल सही है।
सर्वव्यापी

0

शीर्ष मतदान के जवाब के लिए। मैं रैवल्यू संदर्भ से गुजरना पसंद करता हूं।

मैं समझता हूं कि रैवेल्यू रेफरेंस से गुजरने में क्या समस्या है। लेकिन इस समस्या को दो पक्षों में विभाजित करते हैं:

  • फोन करने वाले के लिए:

मैं कोड लिखना चाहिए Base newBase(std::move(<lvalue>))या Base newBase(<rvalue>)

  • तसल्ली के लिए:

लाइब्रेरी लेखक को यह गारंटी देनी चाहिए कि यदि वह स्वयं का स्वामित्व चाहता है तो उसे सदस्य को इनिशियलाइज़ करने के लिए वास्तव में unique_ptr ले जाएगा।

बस इतना ही।

यदि आप प्रतिद्वंद्विता संदर्भ से गुजरते हैं, तो यह केवल एक "चाल" निर्देश को लागू करेगा, लेकिन यदि मूल्य से गुजरता है, तो यह दो है।

हां, यदि पुस्तकालय लेखक इस बारे में विशेषज्ञ नहीं है, तो वह सदस्य को इनिशियलाइज़ करने के लिए unique_ptr नहीं ले सकता है, लेकिन यह लेखक की समस्या है, आप नहीं। यह जो भी मान या संदर्भ संदर्भ से गुजरता है, आपका कोड समान है!

यदि आप एक पुस्तकालय लिख रहे हैं, तो अब आप जानते हैं कि आपको इसकी गारंटी देनी चाहिए, इसलिए बस इसे करें, मूल्य के मुकाबले प्रतिद्वंद्वियों के संदर्भ से गुजरना बेहतर विकल्प है। क्लाइंट जो आपको लाइब्रेरी का उपयोग करता है, वह सिर्फ एक ही कोड लिखेगा।

अब, आपके प्रश्न के लिए। मैं एक कंस्ट्रक्टर या फ़ंक्शन के लिए एक unique_ptr तर्क कैसे पास करूं?

तुम्हें पता है कि सबसे अच्छा विकल्प क्या है।

http://scottmeyers.blogspot.com/2014/07/should-move-only-types-ever-be-passed.html

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.