كيف تجتاز مقابلة C++: أهم الأسئلة المتقدمة

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

كيف تجتاز مقابلة 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

المدونات

توصيات الكتب