यूनिक_एप्ट्र के साथ क्लास के लिए कंस्ट्रक्टर कॉपी करें


105

मैं उस वर्ग के लिए एक कॉपी कंस्ट्रक्टर कैसे लागू कर सकता हूं जिसके पास एक unique_ptrसदस्य चर है? मैं केवल C ++ 11 पर विचार कर रहा हूं।


9
खैर, आप क्या चाहते हैं कि कॉपी कंस्ट्रक्टर को क्या करना है?
निकोल बोलस

मैंने पढ़ा कि unique_ptr अकारण है। यह मुझे आश्चर्यचकित करता है कि एक वर्ग का उपयोग कैसे किया जाता है जिसमें एक unique_ptr सदस्य चर है std::vector
कोडफैक्स

2
@AbhijitKadam आप unique_ptr की सामग्री की गहरी प्रतिलिपि बना सकते हैं। वास्तव में, यह अक्सर समझदार बात है।
घन

2
कृपया ध्यान दें कि आप संभवतः गलत प्रश्न पूछ रहे हैं। आप शायद अपने वर्ग के लिए एक कॉपी कंस्ट्रक्टर नहीं चाहते हैं unique_ptr, जिसमें आप एक कदम कंस्ट्रक्टर चाहते हैं, यदि आपका लक्ष्य डेटा को एक में डालना है std::vector। दूसरी ओर, C ++ 11 मानक ने स्वचालित रूप से मूव कन्स्ट्रक्टर्स बनाए हैं, इसलिए शायद आप एक कॉपी कंस्ट्रक्टर चाहते हैं ...
यक - एडम नेवरामॉन्ट

3
@codefx वेक्टर तत्वों को कॉपी करने योग्य नहीं होना चाहिए; इसका मतलब सिर्फ इतना है कि वेक्टर प्रतिलिपि योग्य नहीं होगा।
एमएम

जवाबों:


81

चूंकि unique_ptrसाझा नहीं किया जा सकता है, आपको इसकी सामग्री को या तो डीप-कॉपी करने की आवश्यकता है या unique_ptrइसे एक में परिवर्तित करने की आवश्यकता है shared_ptr

class A
{
   std::unique_ptr< int > up_;

public:
   A( int i ) : up_( new int( i ) ) {}
   A( const A& a ) : up_( new int( *a.up_ ) ) {}
};

int main()
{
   A a( 42 );
   A b = a;
}

जैसा कि आप उल्लेख कर सकते हैं, एनपीई ने कॉपी-कॉटर के बजाय एक चाल-कोटर का उपयोग किया है, लेकिन इससे आपकी कक्षा के विभिन्न शब्दार्थों में परिणाम होगा। एक चाल-ctor को सदस्य को स्पष्ट रूप से चलने योग्य बनाना होगा std::move:

A( A&& a ) : up_( std::move( a.up_ ) ) {}

आवश्यक ऑपरेटरों का एक पूरा सेट होने के कारण भी होता है

A& operator=( const A& a )
{
   up_.reset( new int( *a.up_ ) );
   return *this,
}

A& operator=( A&& a )
{
   up_ = std::move( a.up_ );
   return *this,
}

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


4
चाल निर्माताओं का उल्लेख करने लायक हो सकता है?
एनपीई

4
+1, लेकिन चाल निर्माणकर्ता को और भी अधिक जोर दिया जाना चाहिए। एक टिप्पणी में, ओपी का कहना है कि लक्ष्य एक वेक्टर में वस्तु का उपयोग करना है। उसके लिए, मूव कंस्ट्रक्शन और मूव असाइनमेंट केवल आवश्यक चीजें हैं।
जोगोजपन

36
एक चेतावनी के रूप में, उपरोक्त रणनीति सरल प्रकारों के लिए काम करती है int। यदि आपके पास एक unique_ptr<Base>स्टोर है Derived, तो ऊपर वाला टुकड़ा करेगा।
यक्क - एडम नेवरामॉन्ट

5
अशक्त के लिए कोई जाँच नहीं है, इसलिए जैसा कि यह एक अशक्त अनुमंडल की अनुमति देता है। कैसे के बारे मेंA( const A& a ) : up_( a.up_ ? new int( *a.up_ ) : nullptr) {}
रयान Haining

1
@ बहुरूपी स्थितियों में हारने वाला, किसी भी तरह से हटने वाला, या बेकार हो जाएगा (यदि आप हटाने का प्रकार जानते हैं, तो केवल हटाए गए को बदल क्यों?)। किसी भी मामले में, हां, यह एक value_ptr- unique_ptrप्लस डेलेटर / कॉपियर जानकारी का डिज़ाइन है ।
यक्क - एडम नेवरामोंट

46

unique_ptrएक वर्ग में एक के लिए सामान्य मामला वंशानुक्रम का उपयोग करने में सक्षम होना है (अन्यथा एक सादे वस्तु अक्सर ऐसा भी करेगी, RAII देखें)। इस मामले के लिए, इस थ्रेड में अब तक कोई उचित जवाब नहीं है

तो, यहाँ प्रारंभिक बिंदु है:

struct Base
{
    //some stuff
};

struct Derived : public Base
{
    //some stuff
};

struct Foo
{
    std::unique_ptr<Base> ptr;  //points to Derived or some other derived class
};

... और लक्ष्य, जैसा कि कहा जाता है, को Fooअनुकूल बनाना है।

इसके लिए, निहित वर्ग को सही ढंग से कॉपी किया जाना सुनिश्चित करने के लिए किसी को निहित सूचक की गहरी प्रतिलिपि करने की आवश्यकता है।

यह निम्नलिखित कोड जोड़कर पूरा किया जा सकता है:

struct Base
{
    //some stuff

    auto clone() const { return std::unique_ptr<Base>(clone_impl()); }
protected:
    virtual Base* clone_impl() const = 0;
};

struct Derived : public Base
{
    //some stuff

protected:
    virtual Derived* clone_impl() const override { return new Derived(*this); };                                                 
};

struct Foo
{
    std::unique_ptr<Base> ptr;  //points to Derived or some other derived class

    //rule of five
    ~Foo() = default;
    Foo(Foo const& other) : ptr(other.ptr->clone()) {}
    Foo(Foo && other) = default;
    Foo& operator=(Foo const& other) { ptr = other.ptr->clone(); return *this; }
    Foo& operator=(Foo && other) = default;
};

मूल रूप से यहां दो चीजें चल रही हैं:

  • पहला, कॉपी और मूव कंस्ट्रक्टर्स का जोड़ है, जो Fooकि कॉपी कंस्ट्रक्टर के डिलीट होने के बाद निहित रूप unique_ptrसे डिलीट हो जाते हैं। मूव कंस्ट्रक्टर को बस द्वारा जोड़ा जा सकता है = default... जो केवल कंपाइलर को यह बताने के लिए है कि सामान्य चाल कंस्ट्रक्टर को हटाया नहीं जाएगा (यह काम करता है, जैसा कि unique_ptrपहले से ही एक मूव कंस्ट्रक्टर है जो इस मामले में इस्तेमाल किया जा सकता है)।

    के कॉपी कंस्ट्रक्टर के लिए Foo, जैसा कोई कॉपी कंस्ट्रक्टर नहीं है, वैसा ही कोई तंत्र नहीं है unique_ptr। तो, एक नया निर्माण करना है unique_ptr, इसे मूल पॉइंटर की एक प्रति के साथ भरें, और इसे कॉपी किए गए वर्ग के सदस्य के रूप में उपयोग करें।

  • यदि उत्तराधिकार सम्‍मिलित है, तो मूल सूचक की प्रति सावधानीपूर्वक की जानी चाहिए। कारण यह है कि std::unique_ptr<Base>(*ptr)ऊपर दिए गए कोड के माध्यम से एक सरल प्रतिलिपि करने से परिणाम घट जाएगा, अर्थात, ऑब्जेक्ट का केवल आधार घटक कॉपी हो जाता है, जबकि व्युत्पन्न भाग गायब है।

    इससे बचने के लिए, प्रतिलिपि को क्लोन-पैटर्न के माध्यम से किया जाना है। एक आभासी फ़ंक्शन के माध्यम से कॉपी करने का विचार है clone_impl()जो Base*बेस क्लास में रिटर्न करता है। व्युत्पन्न वर्ग में, हालांकि, इसे वापस करने के लिए सहसंयोजक के माध्यम से बढ़ाया जाता है Derived*, और यह सूचक व्युत्पन्न वर्ग की एक नई बनाई गई प्रतिलिपि को इंगित करता है। बेस क्लास तब बेस क्लास पॉइंटर के माध्यम से इस नए ऑब्जेक्ट को एक्सेस कर सकता है Base*, इसे एक में लपेट सकता है unique_ptr, और इसे वास्तविक clone()फ़ंक्शन के माध्यम से वापस कर सकता है जिसे बाहर से बुलाया जाता है।


3
यह स्वीकृत उत्तर होना चाहिए था। बाकी सभी लोग इस धागे में हलकों में जा रहे हैं, बिना इस बात पर ध्यान दिए कि कोई व्यक्ति उस वस्तु की नकल करना चाहेगा, जो unique_ptrप्रत्यक्ष प्रतिगमन अन्यथा करेगी। उत्तर??? वंशानुक्रम
तनवीर बदर

4
हो सकता है कि एक यूनिक_प्रटर का उपयोग तब भी किया जाए जब वे जानते हैं कि ठोस प्रकार को कई कारणों से इंगित किया जा रहा है: 1. यह अशक्त होने की आवश्यकता है। 2. सूचक बहुत बड़ा है और हमारे पास स्टैक स्थान सीमित हो सकता है। अक्सर (1) और (2) एक साथ चलते हैं, इसलिए मौके पर कोई भी अशक्त प्रकारों के लिए पसंद unique_ptrकर सकता है optional
पोंकडूडल

3
दाना मुहावरा एक और कारण है।
emsr

क्या होगा अगर एक आधार वर्ग अमूर्त नहीं होना चाहिए? शुद्ध-विशेष के बिना इसे छोड़ने से रन-टाइम बग हो सकते हैं यदि आप इसे व्युत्पन्न में भूल जाते हैं।
ओलेक्सीज प्लॉटनीकज जूल

1
@ OleksijPlotnyc'kyj: हाँ, यदि आप clone_implआधार को लागू करते हैं , तो संकलक आपको यह नहीं बताएगा कि क्या आप इसे व्युत्पन्न वर्ग में भूल जाते हैं। हालाँकि, आप किसी अन्य आधार वर्ग का उपयोग कर सकते हैं और वहां Cloneableएक शुद्ध आभासी लागू clone_implकर सकते हैं। तब संकलक शिकायत करेगा यदि आप इसे व्युत्पन्न वर्ग में भूल जाते हैं।
davidhigh

11

गहरी प्रतियां बनाने के लिए इस सहायक की कोशिश करें, और जब स्रोत unique_ptr अशक्त हो तो सामना करें।

    template< class T >
    std::unique_ptr<T> copy_unique(const std::unique_ptr<T>& source)
    {
        return source ? std::make_unique<T>(*source) : nullptr;
    }

उदाहरण के लिए:

class My
{
    My( const My& rhs )
        : member( copy_unique(rhs.member) )
    {
    }

    // ... other methods

private:
    std::unique_ptr<SomeType> member;
};

2
क्या यह सही ढंग से कॉपी करेगा अगर स्रोत टी से प्राप्त किसी चीज़ की ओर इशारा करता है?
रोमन शापोवालोव

3
@RomanShapovalov नहीं, शायद नहीं, आपको टुकड़ा करना होगा। उस स्थिति में, समाधान संभवत: आपके टी प्रकार के लिए एक अद्वितीय unique_ptr <T> क्लोन () पद्धति को जोड़ने के लिए होगा, और T से व्युत्पन्न प्रकारों में क्लोन () विधि के ओवरराइड्स प्रदान करेगा। क्लोन विधि एक नया उदाहरण बनाएगी व्युत्पन्न प्रकार और वापस।
स्कॉट लंघम

क्या c ++ में कोई अद्वितीय / स्कोप्ड पॉइंटर्स नहीं हैं या पुस्तकालयों को बढ़ावा देते हैं जिनकी गहरी कॉपी कार्यक्षमता अंतर्निहित है? इन स्मार्ट पॉइंटर्स का उपयोग करने वाले वर्गों के लिए हमारे कस्टम कॉपी कंस्ट्रक्टरों आदि को नहीं बनाना अच्छा होगा, जब हम गहरी कॉपी व्यवहार चाहते हैं, जो अक्सर होता है। बस सोच रहा।
शैडो_मैप

5

डैनियल फ्रे ने प्रतिलिपि समाधान के बारे में उल्लेख किया है, मैं इस बारे में बात करूंगा कि कैसे यूनिक_प्रटर को स्थानांतरित किया जाए

#include <memory>
class A
{
  public:
    A() : a_(new int(33)) {}

    A(A &&data) : a_(std::move(data.a_))
    {
    }

    A& operator=(A &&data)
    {
      a_ = std::move(data.a_);
      return *this;
    }

  private:
    std::unique_ptr<int> a_;
};

उन्हें मूव कंस्ट्रक्टर और मूव असाइनमेंट कहा जाता है

आप उन्हें इस तरह इस्तेमाल कर सकते हैं

int main()
{
  A a;
  A b(std::move(a)); //this will call move constructor, transfer the resource of a to b

  A c;
  a = std::move(c); //this will call move assignment, transfer the resource of c to a

}

आपको std :: a और c को लपेटने की आवश्यकता है: क्योंकि उनके पास एक std नाम है: चाल संकलक से कह रही है कि मान को किसी भी संदर्भ में मानों को बदलने के लिए कहें जो कुछ भी हो तकनीकी अर्थों में, std :: चाल कुछ इस तरह है " std :: rvalue "

आगे बढ़ने के बाद, unique_ptr का संसाधन दूसरे अनूठे_ptr पर स्थानांतरित हो जाता है

ऐसे कई विषय हैं जो दस्तावेज़ का संदर्भ देते हैं; यह शुरू करने के लिए एक बहुत आसान है

संपादित करें:

स्थानांतरित वस्तु मान्य लेकिन अनिर्दिष्ट स्थिति में रहेगी

C ++ प्राइमर 5, ch13 भी ऑब्जेक्ट को "स्थानांतरित" करने के तरीके के बारे में बहुत अच्छी व्याख्या देता है


1
तो astd :: move (a) को bमूव कंस्ट्रक्टर में कॉल करने के बाद क्या होता है ? क्या यह पूरी तरह से अमान्य है?
डेविड डोरिया

3

मेरा सुझाव है कि make_unique का उपयोग करें

class A
{
   std::unique_ptr< int > up_;

public:
   A( int i ) : up_(std::make_unique<int>(i)) {}
   A( const A& a ) : up_(std::make_unique<int>(*a.up_)) {};

int main()
{
   A a( 42 );
   A b = a;
}

-1

unique_ptr यह प्रतिलिपि करने योग्य नहीं है, यह केवल जंगम है।

यह सीधे तौर पर टेस्ट को प्रभावित करेगा, जो कि, आपके दूसरे में, उदाहरण भी केवल चल और प्रतिलिपि योग्य नहीं है।

वास्तव में, यह अच्छा है कि आप उपयोग करें unique_ptrजो आपको एक बड़ी गलती से बचाता है।

उदाहरण के लिए, आपके पहले कोड के साथ मुख्य मुद्दा यह है कि पॉइंटर को कभी भी डिलीट नहीं किया जाता है जो वास्तव में, वास्तव में खराब है। कहो, आप इसे ठीक कर देंगे:

class Test
{
    int* ptr; // writing this in one line is meh, not sure if even standard C++

    Test() : ptr(new int(10)) {}
    ~Test() {delete ptr;}
};

int main()
{       
     Test o;
     Test t = o;
}

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

जब एक Testनष्ट हो जाता है, तो यह सूचक को भी नष्ट कर देगा। जब आपका दूसरा Testनष्ट हो जाता है, तो यह सूचक के पीछे की मेमोरी को हटाने की कोशिश करेगा, साथ ही साथ। लेकिन इसे पहले ही डिलीट कर दिया गया है और हमें कुछ खराब मेमोरी एक्सेस रनटाइम एरर (या अगर हम अनलकी हैं तो अपरिभाषित व्यवहार) मिलेगा।

तो, सही तरीका यह है कि या तो कॉपी कंस्ट्रक्टर और कॉपी असाइनमेंट ऑपरेटर को लागू किया जाए, ताकि व्यवहार स्पष्ट हो और हम एक कॉपी बना सकें।

unique_ptrयहाँ हमारे आगे रास्ता है। इसका अर्थ अर्थ होता है: " मैं हूँ unique, इसलिए तुम मुझे कॉपी नहीं कर सकते। " इसलिए, यह हमें अब ऑपरेटरों को लागू करने की गलती से रोकता है।

आप विशेष व्यवहार के लिए कॉपी कंस्ट्रक्टर और कॉपी असाइनमेंट ऑपरेटर को परिभाषित कर सकते हैं और आपका कोड काम करेगा। लेकिन आप ऐसा करने के लिए मजबूर हैं, (इसलिए!)।

कहानी का नैतिक: हमेशा unique_ptrइस तरह की स्थितियों में उपयोग करें ।

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