مقدمة: لماذا يعتبر تحويل الأنواع في لغة C++ حقل ألغام
\n\nهل سبق لك أن كتبت شيئًا مثل (int)someFloat وأملت أن يكون كل شيء على ما يرام؟ أو ربما واجهت موقفًا حيث يتم تجميع البرنامج ولكنه يتعطل في وقت التشغيل مع خطأ غامض؟ مرحبًا بك في عالم تحويل الأنواع في لغة C++ — المكان الذي يفقد فيه حتى المطورون ذوو الخبرة ساعات في التصحيح.
السؤال "Regular cast vs. static_cast vs. dynamic_cast" على Stack Overflow حصد عشرات الآلاف من المشاهدات. وهذا ليس من قبيل الصدفة. كل نوع من أنواع التحويل هو أداة لها غرضها الخاص، واستخدام الأداة الخاطئة يمكن أن يؤدي إلى سلوك غير محدد، أو تسرب للذاكرة، أو ببساطة إلى كود لا يعمل.
\n\nفي هذه المقالة، سنقوم بتحليل ثلاث طرق رئيسية لتحويل الأنواع في لغة C++، وسنعرض الأخطاء الأكثر شيوعًا ونقدم توصيات واضحة حول كيفية تجنبها. في النهاية، ستعرف بالضبط أي cast يجب استخدامه في كل موقف محدد.
\n\n1. Regular cast (C-style cast) — إرث الماضي
\n\nالطريقة القديمة الجيدة C-style cast — هي أبسط طريقة للتحويل، ولكنها في نفس الوقت الأكثر خطورة. تبدو هكذا: (NewType)expression. في لغة C++، تعتبر "regular"، ولكن سلوكها قد يفاجئك بشدة.
كيف يعمل هذا؟
\nيحاول مترجم C++ تفسير C-style cast كتسلسل من static_cast و const_cast و reinterpret_cast. يختار أول مجموعة مناسبة. المشكلة هي أنك لا تعرف أبدًا أي cast سيعمل في النهاية.
مثال على خطأ: تغيير الثبات بشكل عشوائي
\nconst int value = 42;\nint* ptr = (int*)&value; // C-style cast يزيل const\n*ptr = 100; // سلوك غير محدد!\ncout << value; // قد يطبع 42 أو 100 أو أي شيء\n\nيتم تجميع هذا الكود بدون أخطاء، ولكنه يؤدي إلى سلوك غير محدد. const_cast كان سيكون أكثر صدقًا هنا — فهو على الأقل يظهر نيتك.
متى لا يزال C-style cast مقبولاً؟
\n- \n
- أبدًا في لغة C++ الحديثة (C++11 وما بعده). \n
- فقط لتحويلات رقمية بسيطة جدًا حيث يكون الخطر ضئيلًا (مثل
(float)intValue)، ولكن حتى في هذه الحالة من الأفضل استخدامstatic_cast. \n
الخلاصة: انسَ أمر C-style cast. فهو يجعل الكود غير قابل للقراءة وغير آمن. حتى المترجمات الحديثة قد تصدر تحذيرات على مثل هذه الإنشاءات.
\n\n2. static_cast — الجندي العالمي
\n\nstatic_cast — هو أكثر أنواع التحويل استخدامًا في لغة C++. يقوم بإجراء فحص في مرحلة التجميع وهو مناسب لمعظم المواقف التي يكون فيها تحويل الأنواع صحيحًا منطقيًا.
متى تستخدم static_cast؟
\n- \n
- تحويل الأنواع الرقمية:
double←int،int←float. \n - Upcast (تحويل مؤشر من الفئة المشتقة إلى مؤشر من الفئة الأساسية). \n
- تحويل صريح لـ
void*مرة أخرى إلى نوع محدد. \n - استدعاء المنشئات ذات وسيطة واحدة (ولكن من الأفضل استخدام
explicit). \n
مثال: upcast صحيح
\nclass Base { public: virtual void speak() { cout << "Base"; } };\nclass Derived : public Base { public: void speak() override { cout << "Derived"; } };\n\nDerived d;\nBase* b = static_cast<Base*>(&d); // upcast — آمن\nb->speak(); // سيطبع "Derived" بفضل الدالة الافتراضية\n\nالخطأ الأكبر #1: Slicing عند التمرير بالقيمة
\nواحدة من أكثر الأخطاء غدرًا هي slicing (تقطيع الكائن). تحدث عندما تقوم بتحويل كائن من فئة مشتقة إلى الفئة الأساسية بالقيمة، وليس بالمرجع أو المؤشر