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

شرح C++

التحويل بين الأنواع (Type Casting)

الدرس 31 من 34· ⏱ 4 دقائق قراءة

ليش نحتاج تحويلًا صريحًا أصلًا؟

بعض التحويلات بين الأنواع تحصل تلقائيًا (implicit) — مثل int إلى double. لكن أحيانًا تريد تحويلًا لا يحصل تلقائيًا، أو تريد إجباره رغم خطورته. بلغة C كان الحل عامل تحويل واحد غامض:

double price = 19.99;
int cents = (int)(price * 100); // C-style cast — يعمل لكن لا يوضّح نيّتك

المشكلة: (int) قد يعني تحويلًا رقميًا آمنًا، أو قد يعني إعادة تفسير بتّات الذاكرة بشكل خطير — ولا تعرف أيّهما من مجرّد قراءة السطر. C++ تحلّ هذا بأربعة عوامل تحويل صريحة، كل واحد له نيّة واضحة ومترجم يتحقّق منها بقدر معيّن.

static_cast — التحويل المعروف وقت الترجمة

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

double price = 19.99;
int cents = static_cast<int>(price * 100); // نفس النتيجة، لكن النيّة واضحة

int code = static_cast<int>(SomeEnum::Active); // enum إلى int

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

Dog d;
Animal* a = &d;               // تصعيد آمن ضمني أصلًا
Dog* d2 = static_cast<Dog*>(a); // تنزيل — بلا تحقّق: لازم تتأكّد بنفسك أن a فعلًا Dog

dynamic_cast — التنزيل الآمن وقت التشغيل

مخصّص للتنزيل (downcast) بتسلسل وراثي فيه تعدّد أشكال — أي أن الصنف الأساس يملك دالة virtual واحدة على الأقل. يتحقّق وقت التشغيل فعليًا من نوع الكائن:

class Shape { public: virtual ~Shape() = default; };
class Circle : public Shape { public: void draw() { /* ... */ } };
class Square : public Shape {};

void render(Shape* s) {
    Circle* c = dynamic_cast<Circle*>(s);
    if (c) {              // نجح فقط لو s يشير فعليًا لـ Circle
        c->draw();
    } else {
        // فشل التحويل — c هنا nullptr، بلا انهيار
    }
}

مع المؤشّرات، الفشل يُرجع nullptr. مع المراجع، الفشل يرمي استثناء std::bad_cast لأنه لا يوجد "مرجع فارغ" يُرجعه بدلًا من ذلك.

const_cast — إضافة أو إزالة const

العامل الوحيد المخصّص لتعديل صفة const/volatile فقط، دون تغيير النوع الأساسي:

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

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

💡 استخدامه آمن فقط لو الكائن الأصلي لم يُعرَّف const أصلًا (كما بالمثال أعلاه، حيث msg مجرّد معامل بمرجع ثابت). تعديل قيمة أُنشئت const من الأساس عبر const_cast سلوك غير معرَّف (undefined behavior) — قد يعمل بالصدفة وينهار لاحقًا.

reinterpret_cast — إعادة تفسير البتّات

الأخطر والأقل أمانًا: يعيد تفسير نمط البتّات كنوع مختلف تمامًا، بلا أي علاقة منطقية بين النوعين. مفيد فقط بحالات منخفضة المستوى (تشفير، أنظمة، شبكات):

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

// تحويل مؤشّر إلى عدد صحيح — للتجزئة (hashing) مثلًا
uintptr_t address = reinterpret_cast<uintptr_t>(p);

// تحويل بين نوعي مؤشّرات غير مرتبطين إطلاقًا
char* bytes = reinterpret_cast<char*>(p);

جدول المقارنة

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

لماذا نتجنّب (Type)value القديمة

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

Animal* a = &d;
Dog* bad = (Dog*)a;              // ❌ أي عامل استُخدم فعليًا؟ غير واضح
Dog* good = static_cast<Dog*>(a); // ✅ النيّة صريحة، وسهل البحث عنها بالكود

أخطاء شائعة

  • استخدام static_cast للتنزيل (base → derived) دون التأكّد فعليًا من نوع الكائن — لا يوجد تحقّق، فالخطأ يظهر كسلوك غريب لاحقًا وليس كخطأ فوري.
  • استخدام dynamic_cast على صنف أساس بدون أي دالة virtual — خطأ ترجمة، لأن التحقّق وقت التشغيل يحتاج جدول الدوال الافتراضية (vtable).
  • تجاهل فحص نتيجة dynamic_cast على مؤشّر (قد تكون nullptr) ثم استخدامها مباشرة — انهيار فوري.
  • اللجوء لـ reinterpret_cast لحل مشكلة نوع بدل إعادة تصميم الكود — إشارة شبه أكيدة أن هناك خطأ تصميم أعمق.

🎯 التالي: الربط المُهيكل وstd::tuple — التعامل مع قيم متعددة بأناقة.

شرح التحويل بين الأنواع (Type Casting) — C++ بالعربي
التحويل بين الأنواع (Type Casting)C++ بالعربي · The Code Fix

📚 لمزيد من التعمّق في C++، راجِع مرجع C++ الرسمي.

هل كان هذا الدرس مفيدًا؟