الذاكرة ، في
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 آمنة من سلسلة
الرسائل ، ولا تنشئ أي مؤشرات ترابط إضافية لجمع البيانات المهملة ، ويتم كل شيء في
المُنشئين ، والمدمرين ، ومشغلي التعيين ، لذا إذا قمنا بالكتابة فوق كائننا ولم يشير أي كائن
جذري إليها بعد الآن ، فإننا نرجع الذاكرة المخصصة لكائننا.
شكرا لاهتمامكم!