كيف تجتاز مقابلة C++: أهم الأسئلة المتقدمة
\n\nمقابلة منصب مطور C++ من المستوى المتوسط إلى العليا ليست مجرد اختبار للقواعد النحوية. يرغب المحاورون في رؤية فهم عميق للبنية الداخلية للغة، والقدرة على العمل مع الذاكرة، والقوالب، وتعدد الخيوط. في هذه المقالة، قمنا بتحليل الأسئلة الأكثر شيوعًا وصعوبة التي تُطرح في مقابلات C++ المتقدمة. كل سؤال مصحوب بشرح ومثال كود.
\n\n1. المؤشرات الذكية و RAII
\n\nسؤال المؤشرات الذكية هو سؤال كلاسيكي. من المتوقع ألا تذكر فقط std::unique_ptr و std::shared_ptr و std::weak_ptr، بل تشرح أيضًا بنيتها الداخلية ومتى تستخدم أيًا منها.
سؤال نموذجي: ما الفرق بين unique_ptr و shared_ptr؟ كيف يعمل عداد المراجع في shared_ptr؟ لماذا نحتاج weak_ptr؟
الإجابة: std::unique_ptr هو ملكية حصرية للمورد. لا يمكن نسخه، فقط نقله (دلالات النقل). std::shared_ptr يستخدم عد المراجع (reference counting) للملكية المشتركة. عندما يصل العداد إلى الصفر، يتم حذف الكائن. std::weak_ptr هو "مراقب" لا يزيد عداد المراجع. يُستخدم لمنع التبعيات الدائرية (مثلًا في الرسوم البيانية أو الأشجار).
#include <memory>\n#include <iostream>\n\nstruct Resource {\n Resource() { std::cout << "تم الحصول على المورد\\n"; }\n ~Resource() { std::cout << "تم تحرير المورد\\n"; }\n};\n\nint main() {\n // unique_ptr\n std::unique_ptr<Resource> ptr1 = std::make_unique<Resource>();\n // std::unique_ptr<Resource> ptr2 = ptr1; // خطأ في الترجمة - النسخ ممنوع\n std::unique_ptr<Resource> ptr2 = std::move(ptr1); // النقل مسموح\n\n // shared_ptr\n std::shared_ptr<Resource> sptr1 = std::make_shared<Resource>();\n std::shared_ptr<Resource> sptr2 = sptr1; // النسخ مسموح، العداد = 2\n\n // weak_ptr\n std::weak_ptr<Resource> wptr = sptr1; // العداد لا يزيد\n if (auto locked = wptr.lock()) {\n std::cout << "المورد لا يزال حيًا\\n";\n }\n\n return 0;\n}\n\nسؤال إضافي: كيف تنفذ SharedPtr خاصًا بك؟ من المهم هنا ذكر RAII (اكتساب المورد هو التهيئة) - المبدأ الذي يتم فيه اكتساب المورد في المُنشئ، وتحريره في المُدمّر.
2. المدمرات الافتراضية وجدول الدوال الافتراضية (vtable)
\n\nهذا السؤال يختبر فهم تعدد الأشكال وبنية الدوال الافتراضية في C++.
\n\nسؤال نموذجي: لماذا يجب أن يكون للفئة الأساسية التي تحتوي على دوال افتراضية مُدمّر افتراضي؟ كيف يعمل vtable؟
\n\nالإجابة: إذا لم يكن مُدمّر الفئة الأساسية افتراضيًا، فعند حذف كائن من فئة مشتقة عبر مؤشر للفئة الأساسية، سيتم استدعاء مُدمّر الفئة الأساسية فقط. سيؤدي ذلك إلى تسرب الموارد التي خصصتها الفئة المشتقة.
\n\nكل فئة تحتوي على دالة افتراضية واحدة على الأقل تحتوي على جدول ثابت (vtable). يخزن هذا الجدول مؤشرات لتطبيقات الدوال الافتراضية. كل كائن من هذه الفئة يحتوي على مؤشر مخفي (vptr) يشير إلى vtable الخاص بفئته. يتم استدعاء الدالة الافتراضية من خلال مرجعية مزدوجة: كائن ← vptr ← vtable ← دالة. هذا يضيف حملًا إضافيًا صغيرًا مقارنة بالاستدعاء العادي.
\n\n#include <iostream>\n\nclass Base {\npublic:\n virtual void show() { std::cout << "Base\\n"; }\n virtual ~Base() = default; // مُدمّر افتراضي\n};\n\nclass Derived : public Base {\npublic:\n void show() override { std::cout << "Deriv