فهم معنى المصطلح والمفهوم - راي (اكتساب الموارد هو التهيئة)

StackOverflow https://stackoverflow.com/questions/712639

سؤال

هل يمكن أن تعطينا مطورو C ++ وصفا جيدا لما هو راي، لماذا من المهم، وما إذا كان قد يكون له أي أهمية لغات أخرى؟

أنا فعل تعرف قليلا. أعتقد أنه يقف "اكتساب الموارد" هو التهيئة ". ومع ذلك، هذا الاسم لا يغي مع فهمي (ربما غير صحيح) لما هو راي هو: أحصل على الانطباع بأن Raii هو وسيلة لتهيئة الكائنات على المكدس، عندما تخرج هذه المتغيرات من النطاق، سيقوم المدمرون تلقائيا أن يسمى التسبب في تنظيف الموارد.

فلماذا لا يسمى "استخدام المكدس لتشغيل التنظيف" (utsttc :)؟ كيف تحصل من هناك إلى "راي"؟

وكيف يمكنك أن تجعل شيئا ما على المكدس الذي سيؤدي إلى تنظيف شيء يعيش على كومة؟ أيضا، هل هناك حالات حيث لا يمكنك استخدام Raii؟ هل تجد نفسك متمنيا لجمع القمامة؟ على الأقل جامع القمامة يمكنك استخدامها لبعض الكائنات أثناء ترك الآخرين؟

شكرا.

هل كانت مفيدة؟

المحلول

فلماذا لا يسمى "استخدام المكدس لتشغيل التنظيف" (utsttc :)؟

راي يخبرك بماذا تفعل: الحصول على موردك في منشئ! أود أن أضيف: مورد واحد، منشئ واحد. UTSTTC هو مجرد تطبيق واحد من ذلك، راي أكثر من ذلك بكثير.

تمتص إدارة الموارد. هنا، الموارد هو أي شيء يحتاج إلى تنظيف بعد الاستخدام. تظهر دراسات المشروعات في العديد من المنصات أن غالبية الأخطاء ترتبط بإدارة الموارد - وهي سيئة للغاية على Windows (بسبب العديد من أنواع الكائنات والمخصصات).

في C ++، تكون إدارة الموارد معقدة بشكل خاص بسبب مزيج من الاستثناءات والقوالب (C ++ Style). للحصول على نظرة خاطفة تحت غطاء محرك السيارة، انظر gotw8.).


ضمانات C ++ أن المدمرة يسمى إذا وفقط إذا نجح المنشئ. الاعتماد على ذلك، يمكن أن يحل راي الكثير من المشاكل السيئة التي قد لا يعرفها المتوسع مبرمجها. فيما يلي بعض الأمثلة التي تتجاوز "المتغيرات المحلية الخاصة بي سيتم تدميرها كلما أعود".

دعونا نبدأ بتبسيط مفرط FileHandle فئة توظيف راي:

class FileHandle
{
    FILE* file;

public:

    explicit FileHandle(const char* name)
    {
        file = fopen(name);
        if (!file)
        {
            throw "MAYDAY! MAYDAY";
        }
    }

    ~FileHandle()
    {
        // The only reason we are checking the file pointer for validity
        // is because it might have been moved (see below).
        // It is NOT needed to check against a failed constructor,
        // because the destructor is NEVER executed when the constructor fails!
        if (file)
        {
            fclose(file);
        }
    }

    // The following technicalities can be skipped on the first read.
    // They are not crucial to understanding the basic idea of RAII.
    // However, if you plan to implement your own RAII classes,
    // it is absolutely essential that you read on :)



    // It does not make sense to copy a file handle,
    // hence we disallow the otherwise implicitly generated copy operations.

    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;



    // The following operations enable transfer of ownership
    // and require compiler support for rvalue references, a C++0x feature.
    // Essentially, a resource is "moved" from one object to another.

    FileHandle(FileHandle&& that)
    {
        file = that.file;
        that.file = 0;
    }

    FileHandle& operator=(FileHandle&& that)
    {
        file = that.file;
        that.file = 0;
        return *this;
    }
}

إذا فشل البناء (مع استثناء)، لا توجد وظيفة عضوا أخرى - ولا يسمى حتى المدمرة -.

يتجنب Raii استخدام الكائنات في حالة غير صالحة. إنه يجعل الحياة أسهل بالفعل قبل أن نستخدم الكائن.

الآن، دعونا نلقي نظرة على الأشياء المؤقتة:

void CopyFileData(FileHandle source, FileHandle dest);

void Foo()
{
    CopyFileData(FileHandle("C:\\source"), FileHandle("C:\\dest"));
}

هناك ثلاثة حالات خطأ تتعامل معها: لا يمكن فتح أي ملف، يمكن فتح ملف واحد فقط، يمكن فتح كلا الملفين ولكن نسخ الملفات فشلت. في تنفيذ غير راي، Foo سوف تضطر إلى التعامل مع الحالات الثلاث صراحة.

يطلق Raii الموارد التي تم الحصول عليها، حتى عند الحصول على موارد متعددة في بيان واحد.

الآن، دعونا إجمال بعض الكائنات:

class Logger
{
    FileHandle original, duplex;   // this logger can write to two files at once!

public:

    Logger(const char* filename1, const char* filename2)
    : original(filename1), duplex(filename2)
    {
        if (!filewrite_duplex(original, duplex, "New Session"))
            throw "Ugh damn!";
    }
}

منشئ Logger سوف تفشل إذا originalفشل منشئ (لأن filename1 لا يمكن فتحها)، duplexفشل منشئ (لأن filename2 لا يمكن فتحها)، أو الكتابة إلى الملفات في الداخل Loggerفشل جسم المنشئ. في أي من هذه الحالات، Loggerسوف destructor سوف ليس أن يتم استدعاؤها - لذلك لا يمكننا الاعتماد عليها Loggerالتدريس في إطلاق الملفات. لكن اذا original شيدت، وسيتم استدعاء المدمرين أثناء تنظيف Logger البناء.

راي يبسط تنظيف بعد بناء جزئي.


نقاط سلبية:

نقاط سلبية؟ يمكن حل جميع المشاكل مع Raii ومؤشرات ذكية ؛-)

في بعض الأحيان يكون Raii غير عملي عندما تحتاج إلى تأخر الاستحواذ، مما دفع الكائنات المجمعة إلى كومة الكومة.
تخيل الاحتياجات المسجل SetTargetFile(const char* target). وبعد في هذه الحالة، المقبض، الذي لا يزال يحتاج إلى أن يكون عضوا في Logger, ، يحتاج إلى الإقامة في كومة الكومة (على سبيل المثال في مؤشر ذكي، لتشغيل تدمير المقبض بشكل مناسب.)

لم أكن ترغب أبدا في جمع القمامة حقا. عندما أفعل C # أشعر أحيانا لحظة النعيم التي لا أحتاج إليها، ولكن أكثر من ذلك بكثير أفتقد كل الألعاب الرائعة التي يمكن إنشاؤها من خلال الدمار الحاسم. (استخدام IDisposable فقط لا يقطعها.)

لقد كان لدي بنية معقدة واحدة قد استفاد من GC، حيث "المؤشرات الذكية" البسيطة "ستتسبب في إشارات دائرية على فئات متعددة. نشطنا من خلال تحقيق التوازن بعناية من المؤشرات القوية والضعف، ولكن في أي وقت نريد تغيير شيء ما، علينا دراسة مخطط علاقة كبيرة. قد يكون GC أفضل، لكن بعض المكونات التي أجريت موارد يجب أن تصدر في اسرع وقت ممكن.


ملاحظة على عينة filehandle: لم يكن المقصود أن تكون كاملة، مجرد عينة - لكنها تحولت غير صحيحة. شكرا Johannes Schaub للإشارة والفريدوفلو لتحويله إلى محلول C ++ الصحيح. مع مرور الوقت، استقرت مع النهج موثقة هنا.

نصائح أخرى

هناك إجابات ممتازة هناك، لذلك أنا فقط أضف بعض الأشياء المنسية.

0. راي حول النطاقات

راي حول كل من:

  1. اكتساب مورد (بغض النظر عن المورد) في المنشئ، والأمم المتحدة تحصل عليه في المدمر.
  2. يتم تنفيذ المنشئ عند إعلان المتغير، ويتم تنفيذ المدمر تلقائيا عند الخروج من النطاق.

أجاب آخرون بالفعل على ذلك، لذلك لن أشرف.

1. عند الترميز في Java أو C #، يمكنك بالفعل استخدام Raii ...

مونسيو بيردين: ماذا! عندما أقول، "نيكول، أحضر لي النعال، وأعطاني Nightcap،" هذا النثر؟

سيد الفلسفة: نعم يا سيدي.

Monsieur Jourdain: لأكثر من أربعين عاما، كنت أتحدث النثر دون معرفة أي شيء حيال ذلك، وأنا مضطر بك كثيرا لدراسة ذلك.

- مولير: رجل الطبقة الوسطى، الفعل 2، المشهد 4

كما فعلت Monsieur Jourdain مع النثر، C # وحتى Java الناس يستخدمون بالفعل راي، ولكن في طرق خفية. على سبيل المثال، رمز Java التالي (الذي يتم كتابته بنفس الطريقة في C # عن طريق الاستعاضة synchronized مع lock):

void foo()
{
   // etc.

   synchronized(someObject)
   {
      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

... يستخدم بالفعل Raii: يتم اقتناء Mutex في الكلمة الرئيسية (synchronized أو lock)، وسيتم اكتساب الأمم المتحدة عند الخروج من النطاق.

إنه أمر طبيعي للغاية في تدوينه، لا يتطلب أي تفسير تقريبا حتى بالنسبة للأشخاص الذين لم يسمعوا أبدا عن راي.

تتميز ميزة C ++ في Java و C # هنا هو أنه يمكن إجراء أي شيء باستخدام Raii. على سبيل المثال، لا يوجد أي بناء مباشر في ما يعادل synchronized ولا lock في C ++، ولكن لا يزال بإمكاننا الحصول عليها.

في C ++، سيتم كتابته:

void foo()
{
   // etc.

   {
      Lock lock(someObject) ; // lock is an object of type Lock whose
                              // constructor acquires a mutex on
                              // someObject and whose destructor will
                              // un-acquire it 

      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

والتي يمكن كتابة بسهولة طريقة Java / C # (باستخدام وحدات الماكرو C ++):

void foo()
{
   // etc.

   LOCK(someObject)
   {
      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

2. الرايلي لديها استخدامات بديلة

الأرنب الأبيض: [الغناء] أنا متأخر / متأخر / لتاريخ مهم للغاية. / لا وقت ليقول "مرحبا". / مع السلامة. / أنا متأخر، أنا متأخر، أنا متأخر.

- أليس في بلاد العجائب (نسخة ديزني، 1951)

أنت تعرف متى سيتم استدعاء المنشئ (في إعلان الكائن)، وأنت تعرف متى سيتم استدعاء المدمر المقابل (عند الخروج من النطاق)، حتى تتمكن من كتابة رمز سحري تقريبا مع خط. مرحبا بك في Fondland C ++ (على الأقل، من وجهة نظر مطور C ++).

على سبيل المثال، يمكنك كتابة كائن عداد (أترك ذلك كمركز) واستخدمه فقط بإعلان متغيره، كما تم استخدام كائن Lock أعلاه:

void foo()
{
   double timeElapsed = 0 ;

   {
      Counter counter(timeElapsed) ;
      // do something lengthy
   }
   // now, the timeElapsed variable contain the time elapsed
   // from the Counter's declaration till the scope exit
}

أي من بالطبع، يمكن كتابته، مرة أخرى، طريقة Java / C # باستخدام ماكرو:

void foo()
{
   double timeElapsed = 0 ;

   COUNTER(timeElapsed)
   {
      // do something lengthy
   }
   // now, the timeElapsed variable contain the time elapsed
   // from the Counter's declaration till the scope exit
}

3. لماذا لا يفتقر C ++ finally?

الصراخ] إنه أخير العد التنازلي!

- أوروبا: العد التنازلي النهائي (آسف، كنت خارج الاقتباسات، هنا ... :-)

ال finally يتم استخدام جملة في C # / Java للتعامل مع التخلص من الموارد في حالة خروج النطاق (إما عبر return أو استثناء ألقيت).

سيؤدي قراء مواصفات ASTOTE إلى أن C ++ لا يوجد لديه جملة أخيرا. وهذا ليس خطأ، لأن C ++ لا يحتاج إليها، حيث أن Raii تعامل بالفعل بالتخلص من الموارد. (وصدقوني، كتابة مدمرا C ++ هو الأقواس أسهل من كتابة جملة جافا اليمنى أخيرا، أو حتى طريقة التخلص الصحيحة C #).

لا يزال، في بعض الأحيان، finally بند سيكون باردا. هل يمكننا أن نفعل ذلك في C ++؟ نعم نستطيع! ومرة أخرى مع استخدام بديل للرادي.

الخلاصة: راي هي أكثر من الفلسفة في C ++: إنها C ++

راي؟ هذا هو C ++ !!!

- تعليق C ++ Developer's Developer's Duzinger's Developery Develsy Commany من قبل ملك سبارتا غامض و 300 صديق له

عندما تصل إلى مستوى خبرة في C ++، تبدأ التفكير من حيث راي, ، من ناحية الناصرين والمدمرين التنفيذ الآلي.

تبدأ التفكير من حيث نطاقات, ، و ال { و } الشخصيات تصبح تلك الأكثر أهمية في التعليمات البرمجية الخاصة بك.

وكل شيء تقريبا يناسب مباشرة من حيث الراي: السلامة الاستثناء، والمواد المحمولة، واتصالات قاعدة البيانات، وطلبات قاعدة البيانات، واتصال الخادم، والساعات، ومقابض نظام التشغيل، وما إلى ذلك، والأخير، ولكن ليس أقل الذاكرة.

جزء قاعدة البيانات غير مهم، كما، إذا كنت تقبل دفع السعر، يمكنك حتى الكتابة في "البرمجة المعاملات"النمط، خطوط التنفيذ وخطوط التعليمات البرمجية حتى يقرر، في النهاية، إذا كنت ترغب في الالتزام بجميع التغييرات، أو، إذا لم يكن ذلك ممكنا، فقد عادت جميع التغييرات إلى الوراء (طالما أن كل سطر يرضي على الأقل ضمان الاستثناء القوي على الأقل ). (انظر الجزء الثاني من هذا مقالة عشبة لبرمجة المعاملات).

ومثل اللغز، كل شيء يناسب.

Raii هو جزء كبير من C ++، C ++ لا يمكن أن يكون C ++ دون ذلك.

هذا ما يفسر لماذا يتم دمج مطوري C ++ ذوي الخبرة مع راي، ولماذا راي هو أول شيء يبحثون عنه عند محاولة لغة أخرى.

ويشرح سبب وجيزة جامع القمامة، في حين أن قطعة رائعة من التكنولوجيا في حد ذاتها، ليست مثيرة للإعجاب من وجهة نظر مطور C ++:

  • Raii يعالج بالفعل معظم الحالات التي يتم التعامل معها بواسطة GC
  • صفقات GC أفضل من Raii مع مراجع دائرية حول الكائنات المدارة النقية (تخفف من الاستخدامات الذكية من المؤشرات الضعيفة)
  • لا يزال GC يقتصر على الذاكرة، في حين أن الراي يمكن التعامل مع أي نوع من الموارد.
  • كما هو موضح أعلاه، يمكن أن تفعل راي الكثير، أكثر من ذلك بكثير ...

يستخدم Raii دلالات Destructors C ++ لإدارة الموارد. على سبيل المثال، النظر في مؤشر ذكي. لديك منشئ معلمات للمؤشر الذي تهيئة هذا المؤشر مع عنوان الكائن. أنت تخصيص مؤشر على المكدس:

SmartPointer pointer( new ObjectClass() );

عندما يخرج المؤشر الذكي من النطاق، يحذف المدمرة من فئة المؤشر الكائن المتصل. المؤشر هو مكدس المخصص والكائن - تخصيص كومة.

هناك بعض الحالات عندما لا يساعد الراي. على سبيل المثال، إذا كنت تستخدم المؤشرات الذكية عد المرجعية (مثل Boost :: Shared_ptr) وإنشاء هيكل يشبه الرسم البياني مع دورة تخاطر في مواجهة تسرب الذاكرة لأن الكائنات الموجودة في دورة سيمنع بعضها البعض من إصدارها. جمع القمامة سيساعد على هذا.

أنا أتفق مع التهاب المعالجة النباتية. ولكن أود أن أضيف أن الموارد يمكن أن تكون أي شيء ليس فقط الذاكرة. يمكن أن يكون المورد ملف أو قسم حرج أو مؤشر ترابط أو اتصال قاعدة بيانات.

يطلق عليه الاستحواذ على الموارد هو التهيئة لأن المورد يتم الحصول عليها عند إنشاء الكائن المسيطر فوق المورد، إذا فشل المنشئ (أي بسبب استثناء) لا يتم الحصول على المورد. ثم بمجرد الخروج من النطاق يتم إصدار المورد. ضمانات C ++ أن جميع الكائنات الموجودة على المكدس الذي تم إنشاؤه بنجاح سيتم تدميره (يتضمن ذلك منشئين من الفئات الأساسية والأعضاء حتى لو فشل منشئ الفئة الفائقة).

وراء الراي العقلانية هو جعل استثناء الحصول على الموارد آمنة. أن جميع الموارد المكتسبة يتم إطلاقها بشكل صحيح بغض النظر عن مكان حدوث استثناء. ومع ذلك، فإن هذا يعتمد على جودة الفصل الذي يكتسب المورد (يجب أن يكون هذا آمنا استثناء وهذا صعب).

أود أن أضعها بقوة أكبر ثم ردود سابقة.

راي، اكتساب الموارد هو التهيئة يعني أن جميع الموارد المكتسبة يجب الحصول عليها في سياق تهيئة كائن. هذا يمنع اكتساب الموارد "عارية". الأساس المنطقي هو أن التنظيف في C ++ يعمل على أساس كائن، وليس أساس الدالة الدالة. وبالتالي، يجب أن يتم كل التنظيف عن طريق الكائنات، وليس المكالمات وظيفة. في هذا المعنى C ++ هو أكثر وجوه موجهة ثم على سبيل المثال جافا. يعتمد تنظيف Java على مكالمات الوظيفة finally شروط.

المشكلة في جمع القمامة هي أنك تفقد الدمار الحاسم هذا أمر بالغ الأهمية إلى الراي. بمجرد أن يخرج المتغير من النطاق، فإن الأمر متروك لجمع القمامة عند استعادة الكائن. سيستمر الاحتفاظ بالمورد الذي يحتفظ به الكائن حتى يتم استدعاء المدمرة.

يأتي Raii من تخصيص الموارد هو التهيئة. في الأساس، فهذا يعني أنه عندما ينتهي المنشئ من التنفيذ، يتم تهيئة الكائن المبني بالكامل وجاهزا للاستخدام. كما يعني أيضا أن المدمر سيصدر أي موارد (مثل الذاكرة أو موارد نظام التشغيل) المملوكة للكائن.

بالمقارنة مع القمامة التي تم جمعها اللغات / التقنيات (مثل Java، .NET)، C ++ يسمح للتحكم الكامل في حياة كائن. للحصول على كائن مخصص للمكدس، ستعرف متى سيتم استدعاء المدمر للكائن (عند الخروج من النطاق)، الشيء الذي لا يتم التحكم فيه حقا في حالة جمع القمامة. حتى استخدام المؤشرات الذكية في C ++ (مثل دفعة :: Shared_ptr)، ستعرف أنه عندما لا يكون هناك إشارة إلى الكائن المدبب، سيتم استدعاء المدمرة لهذا الكائن.

وكيف يمكنك أن تجعل شيئا ما على المكدس الذي سيؤدي إلى تنظيف شيء يعيش على كومة؟

class int_buffer
{
   size_t m_size;
   int *  m_buf;

   public:
   int_buffer( size_t size )
     : m_size( size ), m_buf( 0 )
   {
       if( m_size > 0 )
           m_buf = new int[m_size]; // will throw on failure by default
   }
   ~int_buffer()
   {
       delete[] m_buf;
   }
   /* ...rest of class implementation...*/

};


void foo() 
{
    int_buffer ib(20); // creates a buffer of 20 bytes
    std::cout << ib.size() << std::endl;
} // here the destructor is called automatically even if an exception is thrown and the memory ib held is freed.

عند وجود مثيل Int_Buffer في وجوده، يجب أن يكون له حجم، وسوف تخصص الذاكرة اللازمة. عندما يخرج من النطاق، يسمى المدمر. هذا مفيد جدا للأشياء مثل كائنات المزامنة. انصح

class mutex
{
   // ...
   take();
   release();

   class mutex::sentry
   {
      mutex & mm;
      public:
      sentry( mutex & m ) : mm(m) 
      {
          mm.take();
      }
      ~sentry()
      {
          mm.release();
      }
   }; // mutex::sentry;
};
mutex m;

int getSomeValue()
{
    mutex::sentry ms( m ); // blocks here until the mutex is taken
    return 0;  
} // the mutex is released in the destructor call here.

أيضا، هل هناك حالات حيث لا يمكنك استخدام Raii؟

لا ليس بالفعل كذلك.

هل تجد نفسك متمنيا لجمع القمامة؟ على الأقل جامع القمامة يمكنك استخدامها لبعض الكائنات أثناء ترك الآخرين؟

أبدا. تحل مجموعة القمامة فقط تحل مجموعة فرعية صغيرة جدا من إدارة الموارد الديناميكية.

هناك بالفعل الكثير من الإجابات الجيدة هنا، لكنني أود فقط إضافة:
تفسير بسيط ل Raii هو أنه في C ++، يتم تدمير كائن مخصص على المكدس كلما خرج من النطاق. هذا يعني أنه سيتم استدعاء كائنات Destructor ويمكن أن تفعل كل التنظيف اللازم.
هذا يعني أنه إذا تم إنشاء كائن بدون "جديد"، لا يلزم "حذف". وهذه هي الفكرة أيضا وراء "المؤشرات الذكية" - إنهم يقيمون على المكدس، والأغطية بشكل أساسي كائن مقره الكومة.

Raii هي اختصار للحصول على الحصول على الموارد هو التهيئة.

هذه التقنية فريدة من نوعها للغاية إلى C ++ بسبب دعمها لكل من المانحين والمدمرين وتوصيل المنشائعين تقريبا الذين يتوافقون على أن الحجج التي يتم تمريرها في أو أسوأ حالة يتم استدعاء المنشئ الافتراضي والمدمرينات إذا كان العرضي الذي يتم توفيره على خلاف ذلك الافتراضي يتم إضافة ذلك بواسطة برنامج التحويل البرمجي C ++ إذا لم تكتب مدمرا صريحا لفئة C ++. يحدث هذا فقط لكائنات C ++ التي تتم إدارةها تلقائيا - وهذا لا يستخدم استخدام المتجر الحر (الذاكرة المخصصة / إلغاء تخصيص باستخدام [] جديد، / حذف / حذف، حذف [] مشغلي ++).

تستخدم تقنية Raii هذه ميزة الكائنات ذات المدار تلقائيا لمعالجة الكائنات التي تم إنشاؤها على جهاز Heap / Stree وبعد سوف تلتف فئة الكائنات التي تم إدارتها تلقائيا هذا كائن آخر يتم إنشاؤه على ذاكرة Heap / Store الحرة. وبالتالي، عند تشغيل منشئ الكائن الذي تم إدارته تلقائيا، يتم إنشاء الكائن ملفوف على الذاكرة المؤقتة (Free-Store) وعندما يخرج مقبض الكائن المدار تلقائيا من النطاق، يسمى Destructor من الكائن الذي تم إدارته تلقائيا تلقائيا فيه يتم تدمير الكائن باستخدام حذف. مع مفاهيم OOP، إذا قمت بلف مثل هذه الكائنات داخل فئة أخرى في نطاق خاص، فلن تتمكن من الوصول إلى أعضاء وأساليب الطبقات الملتفوفة وهذا هو السبب وراء تصميم المؤشرات الذكية (AKA مقبض الفصول). تعرض هذه المؤشرات الذكية هذه الكائن الملتفوف ككائن مكتوب إلى عالم خارجي وهناك من خلال السماح بإحتساء أي أعضاء / طرق يتم تكوين كائن الذاكرة المكشوف من. لاحظ أن المؤشرات الذكية لها نكهات مختلفة بناء على احتياجات مختلفة. يجب أن تشير إلى البرمجة C ++ الحديثة بواسطة Andrei Alexandrescu أو Boost Library (www.boostorg) shared_ptr.hpp التنفيذ / الوثائق لمعرفة المزيد حول هذا الموضوع. آمل أن يساعدك ذلك على فهم الراي.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top