مقدمة: لماذا لا تغفر C++ أخطاء تحويل الأنواع؟
مرحبًا يا صديقي! إذا كنت قد كتبت أي كود بلغة C++ من قبل، فبالتأكيد واجهت موقفًا يشتكي فيه المترجم من عدم تطابق الأنواع. وهنا، على الأرجح، تتذكر التحويل القديم الجيد على طريقة C: (int)x. هل يعمل؟ نعم. هل هو آمن؟ لا. هل هو احترافي؟ أقل من ذلك.
في لغة C++، هناك أربعة عوامل تحويل مدمجة، كل منها يحل مشكلة معينة: static_cast, dynamic_cast, const_cast و reinterpret_cast. استخدامها بشكل صحيح يعني كتابة كود لا يعمل فقط، بل يكون مفهومًا للمطورين الآخرين. والأهم من ذلك - لا يتعطل في وقت التشغيل بأخطاء غامضة.
في هذا الدليل، سنشرح كل تحويل بأمثلة واقعية، ونفهم متى نستخدم أيًا منها، ومتى يكون من الأفضل عدم التحويل إطلاقًا. هيا بنا!
static_cast: صديقك الموثوق للتحويلات العادية
static_cast هو التحويل الأكثر شيوعًا وأمانًا. يُستخدم للتحويلات الصريحة بين الأنواع المرتبطة: الأنواع الرقمية، المؤشرات لأعلى/لأسفل في التسلسل الهرمي للوراثة (بدون فحص في وقت التشغيل)، التعدادات إلى أعداد صحيحة والعكس.
متى تستخدم static_cast؟
- تحويل
intإلىdouble(والعكس). - تحويل المؤشرات ضمن نفس التسلسل الهرمي للفئات (upcast - آمن، downcast - يجب أن تكون متأكدًا).
- تحويل
void*إلى نوع محدد. - استدعاء المنشئات الصريحة أو عوامل التحويل.
مثال:
double pi = 3.14159;int approx = static_cast<int>(pi); // 3
class Base { public: virtual ~Base() = default; };class Derived : public Base { public: void foo() {} };
Base* b = new Derived;Derived* d = static_cast<Derived*>(b); // صحيح، لكن بدون فحصd->foo();delete b;مهم: static_cast لا يقوم بفحص الأنواع في وقت التشغيل. إذا كنت متأكدًا أن الكائن هو بالفعل من هذا النوع - استخدمه. إذا لم تكن متأكدًا - انظر إلى dynamic_cast.
dynamic_cast: downcast آمن مع فحص
dynamic_cast هو التحويل الوحيد الذي يعمل فقط مع الفئات متعددة الأشكال (يجب أن تحتوي على دالة افتراضية واحدة على الأقل). يتحقق في وقت التشغيل مما إذا كان يمكن تحويل المؤشر/المرجع بأمان. إذا لم يكن ممكنًا - يُرجع nullptr (للمؤشرات) أو يرمي std::bad_cast (للمراجع).
متى تستخدم dynamic_cast؟
- عندما لا تكون متأكدًا أن الكائن هو من النوع المشتق بالفعل.
- عند العمل مع حاويات غير متجانسة (مثل vector من Base*).
- في أنماط التصميم (مثل Visitor).
مثال:
class Base { public: virtual ~Base() = default; };class Derived : public Base { public: void specific() {} };class Other : public Base {};
vector<Base*> objects = {new Derived, new Other, new Derived};
for (Base* obj : objects) { Derived* d = dynamic_cast<Derived*>(obj); if (d) { d->specific(); // آمن فقط لـ Derived }}العيب: dynamic_cast أبطأ من static_cast لأنه يتطلب RTTI (معلومات نوع وقت التشغيل). لا تفرط في استخدامه في الأجزاء الحساسة للأداء.
const_cast: عندما تحتاج لإزالة الثبات (ولكن بحذر!)
const_cast هو التحويل الوحيد الذي يمكنه إزالة (أو إضافة) مؤهلات cv (const, volatile). يبدو رائعًا، لكن في الممارسة العملية يُستخدم نادرًا وفقط في الحالات القصوى.
لماذا هو مطلوب أصلاً؟ أحيانًا تحصل على مرجع ثابت لكائن، لكنك تحتاج لاستدعاء دالة قديمة غير موسومة بـ const، على الرغم من أنها لا تغير الكائن. أو عند العمل مع مكتبات C.
مثال (صحيح):
void printInt(const int* p) { // قديم