تعرف على مؤشر جامع البيانات المهملة

الذاكرة ، في C ++ كان من الصعب دائمًا العمل معها (إرث مرير من C ) ... هنا C ++ 11 مع std :: shared_ptr تأتي لمساعدتنا.


كما كنت قد خمنت ، إذا لم يكن لهذه البدائية أي مشاكل ، فلن تكون هذه المقالة :)

دعنا ننظر إلى المثال التالي من تسرب ذاكرة كلاسيكية على std :: shared_ptr :

#include <iostream> #include <memory> class Child; class Parent { public: Parent() { std::cout << "Parent()" << std::endl; } ~Parent() { std::cout << "~Parent()" << std::endl; } void createChild() { child_ptr_ = std::make_shared<Child>(); } std::shared_ptr<Child> getChild() { return child_ptr_; } private: std::shared_ptr<Child> child_ptr_; }; class Child { public: Child() { std::cout << "Child()" << std::endl; } ~Child() { std::cout << "~Child()" << std::endl; } void setParent(std::shared_ptr<Parent> parentPtr) { parent_ptr_ = parentPtr; } private: std::shared_ptr<Parent> parent_ptr_; }; int main() { auto parent = std::make_shared<Parent>(); parent->createChild(); parent->getChild()->setParent(parent); return 0; } 

من الواضح ، أننا لن نرى دعوة من المدمرات الكائن. كيف تتعامل معها؟ الأمراض المنقولة جنسيا :: ضعيف يأتي إلى مساعدتنا:

 ... class Child { ... void setParent(std::shared_ptr<Parent> parentPtr) { parent_ptr_ = parentPtr; } private: std::weak_ptr<Parent> parent_ptr_; }; ... 

نعم ، إنها تساعد في حل المشكلة. ولكن إذا كان لديك تسلسل هرمي أكثر تعقيدًا للكائنات ومن الصعب للغاية فهم من الذي يجب عليه القيام بالأمراض المنقولة جنسياً: ضعيفاً ، ومن ينبغي أن يكون الأمراض المنقولة جنسياً :: shared_ptr ؟ أو لا تريد أن تتعطل مع اتصالات فضفاضة على الإطلاق؟

جامع القمامة هو كل شيء لدينا!

لا ، بالطبع لا. في C ++ ، لا يوجد دعم أصلي لـ Garbage Collector ، وحتى إذا قمنا بإضافته ، فسوف نحصل على التكاليف العامة لـ Garbage Collector ، بالإضافة إلى فواصل RAII.

ماذا نفعل؟

Deterministic Garbage Collector Pointer هو مؤشر يتتبع جميع الروابط من الكائنات الجذرية وبمجرد أن لا تشير أي من الكائنات الجذرية إلى كائننا ، يتم حذفه على الفور.

يشبه مبدأ التشغيل الخاص به std :: shared_ptr (يتتبع النطاق ) ، ولكنه يشبه أيضًا الكائنات التي تشير إليه.

دعونا نلقي نظرة على مبدأ عملها في المثال السابق:

 #include <iostream> #include "gc_ptr.hpp" class Child; class Parent { public: Parent() { std::cout << "Parent()" << std::endl; } ~Parent() { std::cout << "~Parent()" << std::endl; } void createChild() { child_ptr_.create_object(); } memory::gc_ptr<Child> getChild() { return child_ptr_; } void connectToRoot(void * rootPtr) { child_ptr_.connectToRoot(rootPtr); } void disconnectFromRoot(bool isRoot, void * rootPtr) { child_ptr_.disconnectFromRoot(isRoot, rootPtr); } private: memory::gc_ptr<Child> child_ptr_; }; class Child { public: Child() { std::cout << "Child()" << std::endl; } ~Child() { std::cout << "~Child()" << std::endl; } void setParent(memory::gc_ptr<Parent> parentPtr) { parent_ptr_ = parentPtr; } void connectToRoot(void * rootPtr) { parent_ptr_.connectToRoot(rootPtr); } void disconnectFromRoot(bool isRoot, void * rootPtr) { parent_ptr_.disconnectFromRoot(isRoot, rootPtr); } private: memory::gc_ptr<Parent> parent_ptr_; }; int main() { memory::gc_ptr<Parent> parent; parent.create_object(); parent->createChild(); parent->getChild()->setParent(parent); return 0; } 

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

مكتبة gc_ptr.hpp آمنة من سلسلة الرسائل ، ولا تنشئ أي مؤشرات ترابط إضافية لجمع البيانات المهملة ، ويتم كل شيء في المُنشئين ، والمدمرين ، ومشغلي التعيين ، لذا إذا قمنا بالكتابة فوق كائننا ولم يشير أي كائن جذري إليها بعد الآن ، فإننا نرجع الذاكرة المخصصة لكائننا.

شكرا لاهتمامكم!

Source: https://habr.com/ru/post/ar462583/


All Articles