كيف تجتاز مقابلة C++: أهم الأسئلة المتقدمة
مقابلة منصب مطور C++ هي دائمًا تحدٍ. إذا كنت تعرف على الأرجح الأسئلة الأساسية (الفرق بين new و malloc، ما هو المدمر الافتراضي)، فإن المستوى المتقدم يتطلب فهمًا عميقًا للبنية الداخلية للغة. في هذه المقالة، سنناقش الموضوعات الرئيسية التي ستظهر بالتأكيد في المقابلة الفنية لمناصب Middle و Senior. لن نقوم فقط بسرد الأسئلة، بل سنقدم إجابات مفصلة مع أمثلة كود.
1. المؤشرات الذكية وبنيتها الداخلية
السؤال: "اشرح الفرق بين std::unique_ptr و std::shared_ptr؟ كيف يتم تحقيق أمان الخيوط في عداد المراجع؟"
هذا سؤال كلاسيكي. يريد المحاور أن يرى أنك تفهم ليس فقط بناء الجملة، بل أيضًا دلالات ملكية الموارد.
- std::unique_ptr — ملكية حصرية. لا يُنسخ، بل يُنقل فقط. مثالي للموارد التي لها مالك واحد.
- std::shared_ptr — ملكية مشتركة. يستخدم عداد مراجع (control block). عندما ينخفض العداد إلى الصفر، يتم تدمير المورد.
- std::weak_ptr — "مراقب". لا يزيد عداد المراجع. يُستخدم لكسر التبعيات الدائرية في
shared_ptr.
تفصيل متقدم: عداد المراجع في shared_ptr آمن للخيوط (يستخدم عمليات ذرية)، لكن هذا لا يجعل الكائن المُدار نفسه آمنًا للخيوط. يوجد أيضًا عدادان: للمراجع القوية (strong) والمراجع الضعيفة (weak). طالما يوجد مرجع ضعيف واحد على الأقل، لا يتم حذف control block، حتى لو كان الكائن قد دُمّر بالفعل.
#include <memory>#include <iostream>
class Resource {public: Resource() { std::cout << "Resource acquired"; } ~Resource() { std::cout << "Resource destroyed"; } void doWork() { std::cout << "Working..."; }};
int main() { std::unique_ptr<Resource> uptr = std::make_unique<Resource>(); uptr->doWork(); // std::unique_ptr<Resource> uptr2 = uptr; // خطأ في الترجمة! std::unique_ptr<Resource> uptr2 = std::move(uptr); // صحيح
std::shared_ptr<Resource> sptr1 = std::make_shared<Resource>(); std::shared_ptr<Resource> sptr2 = sptr1; // عداد المراجع = 2 sptr1.reset(); // العداد = 1 sptr2.reset(); // العداد = 0، تم تدمير الكائن return 0;}2. الجداول الافتراضية (vtable) وآلية الربط المتأخر
السؤال: "كيف تعمل الدالة الافتراضية على مستوى الذاكرة؟ أين يتم تخزين vtable؟"
هذا السؤال يختبر فهمك ليس فقط لتعدد الأشكال، بل أيضًا لبنية الذاكرة في C++.
- لكل فئة تحتوي على دالة افتراضية واحدة على الأقل، يقوم المترجم بإنشاء جدول ثابت (vtable). يحتوي على مؤشرات للدوال الافتراضية.
- كل كائن من هذه الفئة يحتوي على مؤشر مخفي
__vptr، يشير إلى vtable الخاص بفئته. - عند استدعاء دالة افتراضية عبر مؤشر أو مرجع، يُنشئ المترجم كودًا ينتقل عبر
__vptrإلى vtable، ويجد الدالة المطلوبة حسب الإزاحة ويستدعيها. - يتم تخزين vtable في ذاكرة ثابتة (كمتغير عام)، وليس في المكدس أو الكومة.
تفصيل متقدم: التعقيدات مع الوراثة المتعددة. في هذه الحالة، قد يحتوي الكائن على عدة __vptr، وتنشأ الحاجة إلى دوال thunk (لتصحيح مؤشر this). هذا أحد الأسباب التي تجعل الوراثة المتعددة في C++ تعتبر خطيرة وبطيئة.
#include <iostream>
class Base {public: virtual void foo() { std::cout << "Base::foo"; } virtual ~Base() = def