تخطَّ إلى المحتوى

٢١ يوليو ٢٠٢٦

static_cast مقابل dynamic_cast مقابل reinterpret_cast: الفرق بالعربي

شارك المقال:

أربعة عوامل تحويل صريحة بـ C++، وكل مبتدئ يمر بلحظة "شو الفرق بينهم فعليًا ومتى أستخدم كل واحد؟" هذا المقال يقارنها جنبًا لجنب مع أمثلة عملية.

الفرق بلمحة

العاملالاستخدام النموذجيتحقّق وقت التشغيل؟الخطورة
static_castأرقام، تعدادات، تسلسل وراثي معروفلامتوسطة
dynamic_castتنزيل آمن بتسلسل فيه virtualنعممنخفضة
const_castإضافة/إزالة const/volatile فقطلامتوسطة
reinterpret_castإعادة تفسير بتّات منخفضة المستوىلاعالية جدًا

static_cast: "أنا متأكّد من هذا التحويل"

يُستخدم لتحويلات منطقية بين أنواع مرتبطة — رقمية، تعدادات، أو مؤشّرات ضمن تسلسل وراثي واحد. المترجم لا يتحقّق وقت التشغيل من صحة تنزيل مؤشّر؛ يثق بحكمك أنت:

class Animal {};
class Dog : public Animal {};

Animal* a = new Dog();
Dog* d = static_cast<Dog*>(a); // بلا تحقّق — لو a ليس فعليًا Dog، سلوك غير معرَّف

dynamic_cast: "تأكّد لي فعليًا وقت التشغيل"

الفرق الجوهري: يتحقّق فعليًا من نوع الكائن وقت التشغيل، وله ثمن أداء مقابل ذلك، ويتطلّب أن يملك الصنف الأساس دالة virtual واحدة على الأقل:

class Shape { public: virtual ~Shape() = default; };
class Circle : public Shape {};

Shape* s = new Circle();

if (Circle* c = dynamic_cast<Circle*>(s)) {
    // نجح فعليًا — c مؤكَّد أنه يشير لـ Circle حقيقي
} else {
    // فشل بهدوء — c هنا nullptr، بلا انهيار
}

💡 القاعدة العملية: استخدم static_cast لما تكون متأكّدًا منطقيًا من صحة التحويل (أو لا تحتاج تنزيلًا أصلًا)، واحفظ dynamic_cast لحالات التنزيل الفعلي غير المؤكّد وقت الترجمة — رغم كلفته الإضافية.

const_cast: العامل المتخصّص الوحيد

على عكس الثلاثة الأخرى، لا يغيّر النوع الأساسي إطلاقًا — فقط صفة const أو volatile. آمن فقط لو الكائن الأصلي لم يُعرَّف const من الأساس:

void legacyPrint(char* text) { /* دالة قديمة لا تقبل const char* */ }

void printMessage(const char* msg) {
    legacyPrint(const_cast<char*>(msg)); // نزيل const لمطابقة التوقيع فقط
}

reinterpret_cast: الأخطر بينهم جميعًا

لا يوجد أي منطق تحويل حقيقي هنا — فقط إعادة تسمية لنفس بتّات الذاكرة كنوع آخر تمامًا، بلا أي علاقة بين النوعين:

int value = 65;
int* p = &value;

uintptr_t address = reinterpret_cast<uintptr_t>(p); // مؤشّر إلى عدد صحيح
char* bytes = reinterpret_cast<char*>(p);            // مؤشّرين غير مرتبطين

مخصّص لحالات منخفضة المستوى فقط (أنظمة، شبكات، hashing). لو وجدت نفسك تحتاجه بكود تطبيقات عادي، هذا شبه مؤكّد إشارة لمشكلة تصميم أعمق تستحق إعادة نظر بدل التحايل عليها.

لماذا لا نستخدم (Type)value القديمة إذًا؟

لأنها غامضة: (int)value أو (Dog*)animalPtr قد تنفّذ أيًّا من العوامل الأربعة أعلاه — أو مزيجًا منها — باختيار صامت من المترجم حسب السياق، دون أن يوضّح السطر نفسه أي نوع تحويل حصل فعليًا. إرشادات C++ Core Guidelines الرسمية (القاعدتان ES.48 وES.49) توصي صراحة بتجنّبها لصالح العوامل الأربعة الصريحة: كل واحد يوثّق نيّتك بوضوح، وأسهل بالبحث عبر الكود الكبير (grep "static_cast" أعطاك كل التحويلات الرقمية مثلًا، بعكس البحث عن أقواس عادية).

الخلاصة

اسأل نفسك بالترتيب: هل التحويل منطقي ومعروف وقت الترجمة؟ → static_cast. هل يحتاج تحقّقًا فعليًا وقت التشغيل بتسلسل فيه تعدّد أشكال؟ → dynamic_cast. هل المطلوب فقط تعديل const/volatile؟ → const_cast. هل لا يوجد خيار آخر بحالة منخفضة المستوى؟ → reinterpret_cast، بحذر شديد.

اقرأ الشرح الكامل بدرس التحويل بين الأنواع بمسار C++، أو راجع كل عامل بمرجع C++: static_cast، dynamic_cast، const_cast، وreinterpret_cast.

📚 مصادر رسمية للتعمّق: freeCodeCamp — مصدر تعلّم البرمجة

الأسئلة الشائعة

هل يمكن استبدال static_cast بـ dynamic_cast دائمًا لأنه أأمن؟

لا يُنصح بذلك. dynamic_cast يحتاج تحقّقًا فعليًا وقت التشغيل (RTTI)، وهذا له كلفة أداء حقيقية، ويتطلّب أن يملك الصنف الأساس دالة virtual واحدة على الأقل. استخدم static_cast لما تكون متأكّدًا من صحة التحويل منطقيًا (تحويلات رقمية، تصعيد بالتسلسل الوراثي)، واحفظ dynamic_cast لحالات التنزيل الفعلي غير المؤكّد النوع وقت الترجمة.

لماذا لا نستخدم (Type)value القديمة دائمًا بما أنها أقصر؟

لأنها غامضة: قد تنفّذ static_cast أو const_cast أو reinterpret_cast — أو مزيجًا منها — باختيار صامت من المترجم حسب السياق، دون أن يوضّح السطر نيّتك. إرشادات C++ Core Guidelines الرسمية (ES.48 وES.49) توصي صراحة بتجنّبها لصالح العوامل الأربعة الصريحة، لأنها توثّق النيّة وأسهل بحثًا بالكود الكبير.

ما الفرق بين فشل dynamic_cast على مؤشّر وفشله على مرجع؟

على مؤشّر، الفشل يُرجع nullptr بهدوء — يجب فحص النتيجة بـ if قبل استخدامها. على مرجع، لا يوجد مرجع فارغ يُرجعه المترجم بديلًا، فيرمي استثناء std::bad_cast بدلًا من ذلك، ويحتاج try/catch للتعامل معه.

اقرأ أيضًا

تصفّح كل المقالات في المدوّنة، أو ابدأ التعلّم من المسارات و خرائط الطريق.