مقدمة: لماذا "أكل" برنامجك كل الذاكرة وكيف تصلح ذلك؟
\n\nهل سبق لك أن كتبت تطبيقًا بلغة C++ كان يعمل بشكل مثالي تمامًا حتى أغلقت النافذة؟ ثم اكتشفت أنه "أكل" جيجابايت من ذاكرة الوصول العشوائي (RAM) ولم يُرجع بايتًا واحدًا؟ أو الأسوأ من ذلك، أن برنامجك تعطل فجأة مع خطأ غامض segmentation fault، وقضيت ساعات تبحث أين نسيت استدعاء delete؟
إذا كان الأمر كذلك — مرحبًا بك في النادي. إدارة الذاكرة اليدوية في C++ هي مثل التلاعب بالمناشير الكهربائية: من الممكن نظريًا، لكن من الأفضل أن تترك ذلك للمحترفين. أو، في حالتنا، — المؤشرات الذكية (smart pointers).
\n\nاليوم سنشرح ما هي هذه الكائنات، وما أنواعها، والأهم من ذلك، متى تستخدم أيًا منها. هذه المادة هي اقتباس من السؤال الشهير على Stack Overflow الذي حصل على آلاف المشاهدات. سنجعلها واضحة، حية، ومع أمثلة. لنبدأ!
\n\nما هو المؤشر الذكي؟ بكلمات بسيطة
\n\nتخيل مؤشرًا عاديًا (int* ptr = new int(5);). إنه مثل مفتاح مخزن الموارد. لقد فتحت المخزن (خصصت ذاكرة)، لكن إذا فقدت المفتاح أو نسيت إغلاق الباب — ستضيع الموارد إلى الأبد. هذا هو تسرب الذاكرة.
المؤشر الذكي هو "مفتاح يُغلق تلقائيًا". إنه فئة غلاف (wrapper class) تخزن بداخلها مؤشرًا عاديًا وتستدعي تلقائيًا delete (أو delete[]) عندما لا تكون هناك حاجة للكائن. عندما يخرج المؤشر الذكي عن النطاق، يعمل مدمره (destructor) ويحرر الذاكرة. سحر؟ لا، إنه RAII (Resource Acquisition Is Initialization) — أحد الركائز الأساسية للغة C++.
بدءًا من C++11، أصبحت المؤشرات الذكية جزءًا من المكتبة القياسية (<memory>). انسَ auto_ptr (إنه قديم وخطير). الآن لدينا ثلاثة أبطال:
- \n
std::unique_ptr— المالك الوحيد. \n std::shared_ptr— مورد بملكية "جماعية". \n std::weak_ptr— "مراقب" لا يتدخل في الحياة. \n
دعنا نشرح كل واحد منهم بأسلوب بسيط ومع كود.
\n\nstd::unique_ptr: "هذا ملكي، وليس لأحد غيري!"
\n\nstd::unique_ptr — هو أبسط وأسرع مؤشر ذكي. يضمن أن ملكية الكائن تعود لمؤشر واحد فقط. لا يمكنك نسخ unique_ptr، ولكن يمكنك نقله (نقل الملكية).
متى تستخدمه؟ دائمًا تقريبًا عندما تنشئ كائنًا في الكومة (heap) وتعرف أنه سيكون له مالك واحد طوال فترة حياته.
\n\nمثال: الإنشاء والاستخدام
\n\n#include <iostream>\n#include <memory> // للمؤشرات الذكية\n\nclass Robot {\npublic:\n Robot(const std::string& name) : name_(name) {\n std::cout << "تم تنشيط الروبوت " << name_ << "!\\n";\n }\n void speak() const { std::cout << name_ << ": مرحبًا أيها العالم!\\n"; }\n ~Robot() { std::cout << "تم تدمير الروبوت " << name_ << ".\\n"; }\nprivate:\n std::string name_;\n};\n\nint main() {\n // إنشاء unique_ptr (لا تستخدم new أبدًا!)\n std::unique_ptr<Robot> r1 = std::make_unique<Robot>("R2-D2");\n\n r1->speak(); // نعمل معه كمؤشر عادي\n\n // std::unique_ptr<Robot> r2 = r1; // خطأ! لا يمكن النسخ\n std::unique_ptr<Robot> r2 = std::move(r1); // ننقل الملكية\n\n if (!r1) {\n std::cout << "r1 أصبح فارغًا الآن، انتقلت الملكية إلى r2\\n";\n }\n\n r2->speak(); // يعمل\n // هنا سيخرج r2 عن النطاق وسيتم تدمير الروبوت تلقائيًا\n return 0;\n}\n\nهام: استخدم std::make_unique<T>(args...) بدلاً من new