অনন্য_এসপিআর দিয়ে একটি শ্রেণীর জন্য কন্সট্রাক্টর অনুলিপি করুন


105

unique_ptrসদস্য ভেরিয়েবল থাকা শ্রেণীর জন্য আমি কীভাবে একটি অনুলিপি নির্মাণকারীকে বাস্তবায়ন করব ? আমি কেবল সি ++ 11 বিবেচনা করছি।


9
আচ্ছা, আপনি অনুলিপি নির্মাণকারী কী করতে চান?
নিকল বোলাস

আমি পড়েছি যে অনন্য_পাত্রটি অনিবার্য। এটি আমার ভাবছি একটি বর্গ একটি মধ্যে একটি unique_ptr সদস্য পরিবর্তনশীল আছে যা ব্যবহার করবেন তোলে std::vector
কোডফেক্স

2
@ অভিজিৎকদম আপনি অনন্য_আপনার সামগ্রীর একটি অনুলিপি তৈরি করতে পারেন। আসলে, এটি প্রায়শই করা বুদ্ধিমান কাজ
কিউবিক

2
দয়া করে নোট করুন যে আপনি সম্ভবত ভুল প্রশ্ন জিজ্ঞাসা করছেন। আপনি সম্ভবত আপনার ক্লাসের জন্য একটি কপি কন্সট্রাক্টর চাইবেন না unique_ptr, আপনি সম্ভবত একটি মুভ কনস্ট্রাক্টর চান, যদি আপনার লক্ষ্যটি এ-তে ডেটা রাখা হয় std::vector। অন্যদিকে, সি ++ 11 স্ট্যান্ডার্ড স্বয়ংক্রিয়ভাবে মুভ কনস্ট্রাক্টর তৈরি করেছে, তাই সম্ভবত আপনি একটি অনুলিপি নির্মাণকারী চান ...
ইয়াক্ক - অ্যাডাম নেভ্রামামন্ট

3
@ কোডেফেক্স ভেক্টর উপাদানগুলি অনুলিপিযোগ্য হতে হবে না; এর ঠিক অর্থ হ'ল ভেক্টর অনুলিপিযোগ্য হবে না।
এমএম

উত্তর:


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;
}

এনপিই উল্লিখিত হিসাবে আপনি কপি-কর্টরের পরিবর্তে একটি মুভ-সেন্টার ব্যবহার করতে পারেন তবে এটি আপনার শ্রেণীর বিভিন্ন শব্দার্থবিজ্ঞানের ফলাফল হতে পারে। একজন মুভ-কর্টারের মাধ্যমে সদস্যকে স্পষ্টভাবে এইভাবে চলনযোগ্য করে তোলা দরকার 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) {}
রায়ান হ্যানিং

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) একসাথে চলে যায়, তাই কোনও এক উপলক্ষ্যে নলযোগ্য প্রকারের unique_ptrচেয়ে বেশি পছন্দ করতে পারে optional
পঙ্কডুডল

3
পিম্পল আইডিয়ম আরেকটি কারণ।
এমএসআর

একটি বেস শ্রেণীর বিমূর্ত না হওয়া উচিত কি? খাঁটি-নির্দিষ্টকারী ছাড়াই এটিকে ছেড়ে যাওয়া রান-টাইম বাগগুলিতে ডেকে আনে যা আপনি যদি উত্পন্নকরণটিতে পুনরায় সংশোধন করতে ভুলে যান।
ওলেকসিজ প্লটনিচ'কিজ

1
@ ওলেকসিজপ্লাটিনিসি'কিজ: হ্যাঁ, আপনি যদি clone_implবেসটি বাস্তবায়ন করেন তবে সংকলক আপনাকে উত্পন্ন শ্রেণিতে ভুলে গেলে তা আপনাকে জানায় না। আপনি তবে অন্য বেস ক্লাসটি ব্যবহার করতে পারেন Cloneableএবং clone_implসেখানে খাঁটি ভার্চুয়াল প্রয়োগ করতে পারেন । তারপরে সংকলকটি অভিযোগযুক্ত হবে যদি আপনি এটি উত্পন্ন শ্রেণিতে ভুলে যান।
দবিধিঘে

11

গভীর অনুলিপিগুলি তৈরি করতে এই সহায়িকার চেষ্টা করুন এবং উত্স অনন্য_পিটারটি শূন্য হলেই তা মোকাবেলা করুন।

    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
@ রোমানশাপোভালভ না, সম্ভবত আপনি কাটবেন না। সেক্ষেত্রে সমাধানটি হ'ল আপনার টি টাইপটিতে ভার্চুয়াল অনন্য_পিটিআর <টি> ক্লোন () পদ্ধতি যুক্ত করা এবং টি থেকে প্রাপ্ত ধরণের ক্লোন () পদ্ধতির ওভাররাইড সরবরাহ করা হবে। ক্লোন পদ্ধতিটি একটি নতুন উদাহরণ তৈরি করবে উত্পন্ন টাইপ এবং এটি ফিরে।
স্কট ল্যাংহাম

সি ++ বা বুস্ট লাইব্রেরিতে কোনও অনন্য / স্কোপড পয়েন্টার নেই যা গভীর অনুলিপি কার্যকারিতা অন্তর্নির্মিত আছে? যখন আমরা গভীর কপির আচরণ চাই, যা প্রায়শই এমন হয়, তখন এই স্মার্ট পয়েন্টারগুলি ব্যবহার করে এমন ক্লাসগুলির জন্য আমাদের কাস্টম অনুলিপি নির্মাণকারীদের তৈরি না করা ভাল। ভাবছি.
ছায়া_ম্যাপ

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

}

আপনাকে স্টাডি :: মুভের মাধ্যমে a এবং c আবদ্ধ করতে হবে কারণ তাদের একটি নাম std আছে: মুভটি কম্পাইলারকে পরামিতিগুলির যাই হোক না কেন মানকে যথাযথ রেফারেন্সে রূপান্তরিত করতে বলছে প্রযুক্তিগত দিক থেকে, std :: সরানো যেমন সাদৃশ্য " এসটিডি :: rvalue "

সরানোর পরে, ইউনিক_পিটারের সংস্থানটি অন্য এক অনন্য_প্টরে স্থানান্তরিত হয়

অনেকগুলি বিষয় রয়েছে যা নথিগুলি মূল্যায়নের রেফারেন্স দেয়; এটি দিয়ে শুরু করা বেশ সহজ

সম্পাদনা করুন:

সরানো বস্তু বৈধ তবে অনির্ধারিত অবস্থায় থাকবে

সি ++ প্রাইমার 5, ch13 এছাড়াও কীভাবে বস্তুকে "সরানো" যায় সে সম্পর্কে খুব ভাল ব্যাখ্যা দেয়


1
সুতরাং aমুভি bকনস্ট্রাক্টরে স্টাড :: মুভ (ক) বলার পরে আপত্তি কী হবে ? এটি কি পুরোপুরি অবৈধ?
ডেভিড ডরিয়া

3

আমি মেক_ ইউনিক ব্যবহারের পরামর্শ দিচ্ছি

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এই জাতীয় পরিস্থিতিতে ব্যবহার করুন।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.