কেন স্টাড :: শেয়ারড_পিটার <ভিড> কাজ করে


129

আমি শাটডাউনে নির্বিচারে ক্লিনআপ সম্পাদন করতে std :: shared_ptr ব্যবহার করে কিছু কোড পেয়েছি। প্রথমে আমি ভেবেছিলাম এই কোডটি সম্ভবত কাজ করতে পারে না তবে আমি নিম্নলিখিতটি চেষ্টা করেছিলাম:

#include <memory>
#include <iostream>
#include <vector>

class test {
public:
  test() {
    std::cout << "Test created" << std::endl;
  }
  ~test() {
    std::cout << "Test destroyed" << std::endl;
  }
};

int main() {
  std::cout << "At begin of main.\ncreating std::vector<std::shared_ptr<void>>" 
            << std::endl;
  std::vector<std::shared_ptr<void>> v;
  {
    std::cout << "Creating test" << std::endl;
    v.push_back( std::shared_ptr<test>( new test() ) );
    std::cout << "Leaving scope" << std::endl;
  }
  std::cout << "Leaving main" << std::endl;
  return 0;
}

এই প্রোগ্রামটি আউটপুট দেয়:

At begin of main.
creating std::vector<std::shared_ptr<void>>
Creating test
Test created
Leaving scope
Leaving main
Test destroyed

এটি কেন কার্যকর হতে পারে সে সম্পর্কে আমার কিছু ধারণাগুলি রয়েছে যা জি ++ এর জন্য বাস্তবায়িত হিসাবে এসটিডি :: শেয়ারড_প্টারগুলির ইন্টার্নালগুলির সাথে করা উচিত। যেহেতু এই বস্তু থেকে ঢালাই কাউন্টার সঙ্গে অভ্যন্তরীণ পয়েন্টার একসঙ্গে মোড়ানো std::shared_ptr<test>করতে std::shared_ptr<void>সম্ভবত বিনাশকারী ডাকে বাধা নয়। এই ধারণাটি কি সঠিক?

এবং অবশ্যই আরও গুরুত্বপূর্ণ প্রশ্ন: এটি কি স্ট্যান্ডার্ড অনুসারে কাজ করার গ্যারান্টিযুক্ত, বা আরও স্ট্যান্ডার্ড :: শেয়ার্ড_পিটারের অভ্যন্তরে পরিবর্তন হতে পারে, অন্যান্য বাস্তবায়ন আসলে এই কোডটি ভেঙে দেয়?


2
পরিবর্তে আপনি কি ঘটবে বলে আশা করেছিলেন?
অরবিটে হালকা ঘোড়দৌড়

1
এখানে কোনও castালাই নেই - এটি শেয়ারড_প্টর <টেস্টে> থেকে ভাগ করে নেওয়া_প্ট্রে <ভিভিড> এ রূপান্তর।
অ্যালান স্টোকস

এফওয়াইআই: এমএসডিএন-তে শেয়ার্ড_পিটিআর সম্পর্কিত একটি নিবন্ধের লিঙ্কটি এখানে রয়েছে: msdn.microsoft.com/en-us/library/bb982026.aspx এবং এটি জিসিসির নথিপত্র: gcc.gnu.org/onlinesocs/libstdc++/latest -ডোজিন / a00267.html
ইয়াসৌসার

উত্তর:


98

কৌতুকটি হ'ল std::shared_ptrধরণ মুছে ফেলা। মূলত, যখন নতুন shared_ptrতৈরি করা হয় এটি অভ্যন্তরীণভাবে একটি deleterফাংশন (যা কনস্ট্রাক্টরের পক্ষে যুক্তি হিসাবে দেওয়া যেতে পারে তবে কলিংয়ের ক্ষেত্রে ডিফল্ট উপস্থিত না হলে delete) সংরক্ষণ করবে। যখন shared_ptrধ্বংস হয়ে যায়, এটি সেই সঞ্চিত ফাংশনটিকে কল করে এবং এটি কল করবে deleter

স্টার্ড :: ফাংশন দিয়ে সরানো সহজ ধরণের ইরেজারের একটি সাধারণ স্কেচ এবং সমস্ত রেফারেন্স গণনা এবং অন্যান্য সমস্যাগুলি এড়ানো এখানে দেখা যাবে:

template <typename T>
void delete_deleter( void * p ) {
   delete static_cast<T*>(p);
}

template <typename T>
class my_unique_ptr {
  std::function< void (void*) > deleter;
  T * p;
  template <typename U>
  my_unique_ptr( U * p, std::function< void(void*) > deleter = &delete_deleter<U> ) 
     : p(p), deleter(deleter) 
  {}
  ~my_unique_ptr() {
     deleter( p );   
  }
};

int main() {
   my_unique_ptr<void> p( new double ); // deleter == &delete_deleter<double>
}
// ~my_unique_ptr calls delete_deleter<double>(p)

যখন shared_ptrঅন্যের কাছ থেকে অনুলিপি করা হয় (বা ডিফল্ট নির্মিত) চারপাশে মুছে ফেলা হয়, যাতে আপনি যখন ডেস্ট্রাক্টরকে কল করবেন তার shared_ptr<T>একটি shared_ptr<U>তথ্য থেকে একটি নির্মাণ করেন তখন এছাড়াও পাসওয়ার্ডটি পাস করা হয় deleter


একটা ভুল ছাপান হবে বলে মনে হয়: my_shared। আমি এটি ঠিক করেছি তবে সম্পাদনা করার কোনও সুযোগ নেই।
আলেক্সি কুকানভ

@ অ্যালেক্সি কুকানভ, @ ডেনিস জিকিফুজ: সম্পাদনা করার জন্য ধন্যবাদ আমি দূরে ছিলাম এবং এটি দেখিনি Thanks
ডেভিড রদ্রিগেজ - ড্রিবিস

2
@ user102008 আপনার 'স্টাডি :: ফাংশন' দরকার নেই তবে এটি কিছুটা নমনীয় (সম্ভবত এখানে কিছুটা আসে না) তবে এটি 'মুছে ফেলা_ডিলেটার <T>' সংরক্ষণ করে কীভাবে মুছে ফেলা যায় তা পরিবর্তন করে না ফাংশন পয়েন্টার 'অকার্যকর (অকার্যকর *)' আপনি সেখানে ক্ষয় করার ধরণটি সম্পাদন করছেন: টি সঞ্চিত পয়েন্টার টাইপ থেকে চলে গেছে।
ডেভিড রদ্রিগেজ - ড্রিবিস

1
এই আচরণটি সি ++ স্ট্যান্ডার্ড দ্বারা গ্যারান্টিযুক্ত, তাই না? আমার এক ক্লাসে আমার টাইপ ইরেজোর দরকার, এবং std::shared_ptr<void>আমাকে কেবল একটি বেহাল র‌্যাপার ক্লাস ঘোষণা করা এড়াতে দেয় যাতে আমি এটি একটি নির্দিষ্ট বেস শ্রেণি থেকে উত্তরাধিকারী হতে পারি।
ভায়োলেট জিরাফ

1
@ অ্যাঞ্জেলাসমর্টিস: সঠিক মুছে ফেলার প্রকারের অংশ নয় my_unique_ptr। যখন mainটেম্পলেটটিতে doubleইনস্টল করা হয় তখন ডিলিটরটি বেছে নেওয়া হয় তবে এটি কোনও ধরণের অংশ নয় my_unique_ptrএবং অবজেক্ট থেকে পুনরুদ্ধার করা যায় না। মুছে ফেলার ধরণটি বস্তুটি থেকে মুছে ফেলা হয়, যখন কোনও ফাংশন একটি পায় my_unique_ptr(রুল্যু-রেফারেন্স দ্বারা বলুন), সেই ফাংশনটি না এবং ডিলারটি কী তা জানে না।
ডেভিড রদ্রিগেজ - ড্রিবিস

35

shared_ptr<T> যৌক্তিকভাবে [*] এর দু'জন প্রাসঙ্গিক ডেটা সদস্য রয়েছে:

  • বস্তু পরিচালিত হচ্ছে একটি পয়েন্টার
  • মুছে ফেলা ফাংশনের একটি পয়েন্টার যা এটি ধ্বংস করতে ব্যবহৃত হবে।

আপনার এর deleter ফাংশন shared_ptr<Test>, আপনি তা নির্মাণ দেওয়া জন্য স্বাভাবিক হয় Test, যা পয়েন্টার পরিবর্তন করে Test*এবং deleteএটা s।

যখন আপনি আপনার ধাক্কা shared_ptr<Test>এর ভেক্টর মধ্যে shared_ptr<void>, উভয় যারা অনুলিপি করা, যদিও প্রথম এক রূপান্তরিত হয় void*

সুতরাং, যখন ভেক্টর উপাদানটি এটির সাথে সর্বশেষ রেফারেন্স গ্রহণ করে ধ্বংস হয়ে যায়, এটি পয়েন্টারটিকে একটি মুছে ফেলাতে পাস করে যা এটি সঠিকভাবে ধ্বংস করে।

এটা আসলে একটু বেশি এই তুলনায় জটিল, কারণ shared_ptrএকটি deleter নিতে পারেন functor মাত্র একটি ফাংশন বদলে, তাই এমনকি প্রতি অবজেক্ট ডেটা মাত্র একটি ফাংশন পয়েন্টার বদলে সংরক্ষণ করা আছে হতে পারে। তবে এই ক্ষেত্রে এর জন্য কোনও অতিরিক্ত ডেটা নেই, কেবলমাত্র কোনও টেমপ্লেট ফাংশন ইনস্ট্যান্টেশন করার জন্য একটি পয়েন্টার সংরক্ষণ করা যথেষ্ট, একটি টেমপ্লেট প্যারামিটার দিয়ে যে ধরণের মাধ্যমে পয়েন্টারটি মুছতে হবে তা অবশ্যই ধারণ করে।

[*] যৌক্তিকভাবে এই অর্থে যে এটিতে তাদের অ্যাক্সেস রয়েছে - তারা নিজেই ভাগ হওয়া_প্টরের সদস্য হতে পারে না তবে এটির নির্দেশিত কিছু পরিচালনার নোডের পরিবর্তে।


2
মুছে ফাংশন / ফান্টকারটি অন্য শেয়ার্ড_পিটার উদাহরণগুলিতে অনুলিপি করা হয়েছে বলে উল্লেখ করার জন্য +1 - তথ্যের এক টুকরো অন্য উত্তরে অনুপস্থিত।
আলেক্সি কুকানভ

এর অর্থ কি এই যে শেয়ারড_প্টার ব্যবহার করার সময় ভার্চুয়াল বেস ডেস্ট্রাক্টরগুলির প্রয়োজন নেই?
রোন্যাগ

হ্যাঁ তবে, আমি এখনও ডিস্ট্রাক্টরকে ভার্চুয়াল করার পরামর্শ দিচ্ছি, যদি আপনার অন্য কোনও ভার্চুয়াল সদস্য থাকে তবে। (দুর্ঘটনাক্রমে একবারে ভুলে যাওয়ার ব্যথা কোনও সম্ভাব্য উপকারের চেয়েও বেশি।)
অ্যালান স্টোকস

হ্যাঁ, আমি রাজি হব। কম আকর্ষণীয়। টাইপ ইরেজর সম্পর্কে আমি জানতাম কেবল এটির এই "বৈশিষ্ট্য "টি বিবেচনা করা হয়নি।
রোন্যাগ

2
@ronag: ভার্চুয়াল ডেস্ট্রাক্টরগুলির প্রয়োজন হয় না যদি আপনি shared_ptrউপযুক্ত টাইপ দিয়ে সরাসরি তৈরি করেন বা আপনি যদি ব্যবহার করেন make_shared। তবে, এটি এখনও একটি ভাল ধারণা হিসাবে পয়েন্টারটির ধরণটি নির্মাণ থেকে পরিবর্তন হতে পারে যতক্ষণ না এটি সংরক্ষণ করা হয় shared_ptr: যতক্ষণ base *p = new derived; shared_ptr<base> sp(p);না shared_ptrউদ্বিগ্ন অবজেক্টটি baseনয় derived, তাই আপনার ভার্চুয়াল ডেস্ট্রাক্টর দরকার। এই প্যাটার্নটি উদাহরণস্বরূপ কারখানার নিদর্শনগুলির সাথে সাধারণ হতে পারে।
ডেভিড রদ্রিগেজ - ড্রিবিস

10

এটি কাজ করে কারণ এটি মুছে ফেলা টাইপ ব্যবহার করে।

মূলত, আপনি যখন একটি তৈরি করেন shared_ptr, এটি একটি অতিরিক্ত যুক্তি পাস করে (যা আপনি চাইলে প্রকৃতপক্ষে সরবরাহ করতে পারেন) এটি ডিলেটর ফান্টেক্টর।

এই ডিফল্ট ফান্টেক্টর আপনার ব্যবহৃত টাইপ করার জন্য পয়েন্টার হিসাবে আর্গুমেন্ট হিসাবে স্বীকার করে shared_ptr, সুতরাং voidএখানে আপনি testএখানে ব্যবহার করা স্ট্যাটিক টাইপটিকে যথাযথভাবে কাস্ট করেন এবং এই বস্তুটিতে ডেস্ট্রাক্টরকে কল করেন।

কোনও পর্যাপ্ত উন্নত বিজ্ঞান যাদুর মতো অনুভব করে, তাই না?


5

কনস্ট্রাক্টর shared_ptr<T>(Y *p)প্রকৃতপক্ষে কল করছে shared_ptr<T>(Y *p, D d)যেখানে dবস্তুর জন্য স্বয়ংক্রিয়ভাবে উত্পন্ন মুছে ফেলা হবে।

যখন এটি ঘটে তখন অবজেক্টের Yধরণটি জানা যায়, সুতরাং এই shared_ptrঅবজেক্টের জন্য মুছে যাওয়া ডিস্ট্রাক্টর জানেন যে কোন ডেস্ট্রাক্টরকে কল করা উচিত এবং যখন পয়েন্টারটি কোনও ভেক্টরে সঞ্চিত থাকে তখন এই তথ্যটি হারিয়ে যায় নাshared_ptr<void>

প্রকৃতপক্ষে চশমা প্রয়োজন যে একটি receving জন্য shared_ptr<T>বস্তু একটি গ্রহণ করতে shared_ptr<U>এটা যে সত্য হতে হবে বস্তু এবং U*পরোক্ষভাবে একটি থেকে পরিবর্তনীয় হওয়া আবশ্যক T*এবং এই অবশ্যই সঙ্গে কেস T=voidকারণ কোনো পয়েন্টার একটি কাজে রূপান্তরিত হতে পারে void*পরোক্ষভাবে। মুছে ফেলা সম্পর্কে কিছুই বলা হয় নি যে অবৈধ হবে তাই চশমাগুলি বাধ্যতামূলক করছে যে এটি সঠিকভাবে কাজ করবে।

প্রযুক্তিগতভাবে আইআইআরসি একটি shared_ptr<T>হাইড অবজেক্টের জন্য একটি পয়েন্টার ধারণ করে যার মধ্যে রেফারেন্স কাউন্টার এবং প্রকৃত বস্তুর পয়েন্টার থাকে; এই লুকানো কাঠামোয় মুছে ফেলা সঞ্চার করার মাধ্যমে shared_ptr<T>নিয়মিত পয়েন্টার হিসাবে এখনও বড় রাখার সময় এই দৃশ্যত যাদু বৈশিষ্ট্যটি কাজ করা সম্ভব (যদিও পয়েন্টারটিকে ডিফারেন্সিংয়ের জন্য একটি দ্বিগুণ অভিশাপ প্রয়োজন

shared_ptr -> hidden_refcounted_object -> real_object

3

Test*স্পষ্টত রূপান্তরিত হয় void*, তাই shared_ptr<Test>স্পষ্টভাবে রূপান্তরিত হয় shared_ptr<void>, মেমরি থেকে। এটি কাজ করে কারণ shared_ptrরান-টাইমে ধ্বংস নিয়ন্ত্রণের জন্য ডিজাইন করা হয়েছে, সংকলন-সময় নয়, তারা অভ্যন্তরীণভাবে যথাযথ ডেস্ট্রাক্টরকে কল করতে উত্তরাধিকারটি ব্যবহার করবে কারণ এটি বরাদ্দের সময় ছিল।


আপনি আরও ব্যাখ্যা করতে পারেন? আমি এখনই একটি অনুরূপ প্রশ্ন পোস্ট করেছি, আপনি সাহায্য করতে পারলে এটি দুর্দান্ত হবে!
ব্রুস

3

আমি এই প্রশ্নের উত্তর দিতে যাচ্ছি (2 বছর পরে) শেয়ারড_প্টারের খুব সরল বাস্তবায়ন যা ব্যবহারকারী বুঝতে পারবে using

প্রথমত আমি কয়েকটি সাইড ক্লাসে যাচ্ছি, শেয়ারড_পিটার_বেস, এসপি_সক্টেন্ট_বেস এসপি_সকন্টেড_আইপিএল, এবং চেক_ডিলেটার যা শেষটি একটি টেম্পলেট।

class sp_counted_base
{
 public:
    sp_counted_base() : refCount( 1 )
    {
    }

    virtual ~sp_deleter_base() {};
    virtual void destruct() = 0;

    void incref(); // increases reference count
    void decref(); // decreases refCount atomically and calls destruct if it hits zero

 private:
    long refCount; // in a real implementation use an atomic int
};

template< typename T > class sp_counted_impl : public sp_counted_base
{
 public:
   typedef function< void( T* ) > func_type;
    void destruct() 
    { 
       func(ptr); // or is it (*func)(ptr); ?
       delete this; // self-destructs after destroying its pointer
    }
   template< typename F >
   sp_counted_impl( T* t, F f ) :
       ptr( t ), func( f )

 private:

   T* ptr; 
   func_type func;
};

template< typename T > struct checked_deleter
{
  public:
    template< typename T > operator()( T* t )
    {
       size_t z = sizeof( T );
       delete t;
   }
};

class shared_ptr_base
{
private:
     sp_counted_base * counter;

protected:
     shared_ptr_base() : counter( 0 ) {}

     explicit shared_ptr_base( sp_counter_base * c ) : counter( c ) {}

     ~shared_ptr_base()
     {
        if( counter )
          counter->decref();
     }

     shared_ptr_base( shared_ptr_base const& other )
         : counter( other.counter )
     {
        if( counter )
            counter->addref();
     }

     shared_ptr_base& operator=( shared_ptr_base& const other )
     {
         shared_ptr_base temp( other );
         std::swap( counter, temp.counter );
     }

     // other methods such as reset
};

এখন আমি দুটি "ফ্রি" ফাংশন তৈরি করতে যাচ্ছি যার নাম Make_sp_counted_impl যা নতুন তৈরি হওয়াতে একটি পয়েন্টার ফিরিয়ে দেবে।

template< typename T, typename F >
sp_counted_impl<T> * make_sp_counted_impl( T* ptr, F func )
{
    try
    {
       return new sp_counted_impl( ptr, func );
    }
    catch( ... ) // in case the new above fails
    {
        func( ptr ); // we have to clean up the pointer now and rethrow
        throw;
    }
}

template< typename T > 
sp_counted_impl<T> * make_sp_counted_impl( T* ptr )
{
     return make_sp_counted_impl( ptr, checked_deleter<T>() );
}

ঠিক আছে, আপনি যখন একটি টেম্প্লেটেড ফাংশনটির মাধ্যমে একটি শেয়ারড_পিটার তৈরি করেন তখন কী হবে তা এই দুটি ফাংশন অপরিহার্য।

template< typename T >
class shared_ptr : public shared_ptr_base
{

 public:
   template < typename U >
   explicit shared_ptr( U * ptr ) :
         shared_ptr_base( make_sp_counted_impl( ptr ) )
   {
   }

  // implement the rest of shared_ptr, e.g. operator*, operator->
};

টি বাতিল হয়ে গেলে এবং ইউটি আপনার "পরীক্ষা" শ্রেণীর হলে উপরেরটি কী হবে তা লক্ষ করুন। এটি টি-তে কোনও পয়েন্টার নয়, ইউ-তে একটি পয়েন্টার সহ Make_sp_counted_impl () কল করবে the ধ্বংসটির পরিচালনা এখানে সমস্ত মাধ্যমে সম্পন্ন। ভাগ করা_পিটার_বেস ক্লাসটি অনুলিপি এবং অ্যাসাইনমেন্ট ইত্যাদি সম্পর্কিত রেফারেন্স গণনা পরিচালনা করে The

সুতরাং আপনার শূন্য করতে একটি শেয়ার্ড_পিটার থাকলেও নীচে আপনি যে ধরণের নতুনটিতে পাস করেছেন তার পয়েন্টার পরিচালনা করছেন। নোট করুন যে আপনি যদি ভাগ করে নেওয়ার আগে আপনার পয়েন্টারটিকে একটি অকার্যকর * রূপান্তরিত করে থাকেন, তবে এটি চেক করা_ডিলিটে সংকলন করতে ব্যর্থ হবে যাতে আপনি সেখানেও নিরাপদে থাকেন।

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