c++ - متى يجب استخدام static_cast و dynamic_cast و const_cast و reinterpret_cast؟




pointers casting (5)

استخدم dynamic_cast لتحويل المؤشرات / المراجع داخل هرمية الوراثة.

استخدم static_cast لتحويلات النوع العادي.

استخدم reinterpret_cast ذات المستوى المنخفض من أنماط البت. استخدم بحذر شديد.

استخدم const_cast لـ casting بعيداً const/volatile . تجنب ذلك ما لم تكن عالقاً باستخدام API const- غير صحيحة.

ما هي الاستخدامات المناسبة لـ:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • (type)value المصبوب من النمط C (type)value
  • نمط المصبوب بأسلوب الوظيفة type(value)

كيف يقرر الشخص الذي يستخدم في أي حالات محددة؟


بالإضافة إلى الإجابات الأخرى حتى الآن ، هنا مثال غير واضح حيث static_cast ليست كافية بحيث تحتاج إلى reinterpret_cast . افترض وجود دالة في معلمة إخراج تقوم بإرجاع المؤشرات إلى كائنات من فئات مختلفة (والتي لا تشارك فئة أساسية مشتركة). مثال حقيقي لمثل هذه الوظيفة هو CoCreateInstance() (انظر المعلمة الأخيرة ، والتي هي في الواقع void** ). افترض أنك طلبت فئة معينة من الكائنات من هذه الوظيفة ، حتى تعرف مقدما نوع المؤشر (الذي غالبا ما تفعله لكائنات COM). في هذه الحالة لا يمكنك إرسال المؤشر إلى المؤشر الخاص بك إلى void** مع static_cast : تحتاج reinterpret_cast<void**>(&yourPointer) .

في الكود:

#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
    CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
    //static_cast<void**>(&pNetFwPolicy2) would give a compile error
    reinterpret_cast<void**>(&pNetFwPolicy2) );

ومع ذلك ، يعمل static_cast للمؤشرات البسيطة (وليس المؤشرات إلى المؤشرات) ، بحيث يمكن إعادة كتابة التعليمات البرمجية أعلاه لتجنب reinterpret_cast (بسعر متغير إضافي) بالطريقة التالية:

#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
void* tmp = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
    CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
    &tmp );
pNetFwPolicy2 = static_cast<INetFwPolicy2*>(tmp);

قد يساعد إذا كنت تعرف القليل من الداخلية ...

static_cast

  • مترجم C ++ بالفعل يعرف كيفية تحويل بين أنواع قشارة مثل تعويم إلى كثافة العمليات. استخدم static_cast لهم.
  • عندما تطلب من المحول البرمجي أن يقوم بالتحويل من النوع A إلى B ، فإن static_cast باستدعاء B منشئ تمرير A كمعلمة. بدلا من ذلك ، يمكن أن يكون لدى A مشغل تحويل (أي A::operator B() ). إذا لم يكن لدى B مُنشئًا من هذا القبيل ، أو إذا لم يكن لدى A عامل تحويل ، فستحصل على خطأ وقت في الترجمة.
  • دائمًا ما ينجح المصبوب من A* إلى B* إذا كان A و B في التسلسل الهرمي (أو باطل) وإلا فإنك تحصل على خطأ في الترجمة.
  • مسكتك : إذا قمت بإدخال المؤشر الأساسي إلى مؤشر مشتق ولكن إذا كان الكائن الفعلي لا يتم اشتقاقه فعليًا فلن تحصل على خطأ. يمكنك الحصول على مؤشر سيئة وعلى الأرجح segfault في وقت التشغيل. وينطبق نفس الشيء على A& B& .
  • مسكتك : المصبوب من Derived إلى Base أو viceversa ينشئ نسخة جديدة ! بالنسبة إلى الأشخاص القادمين من C # / Java ، قد تكون هذه مفاجأة كبيرة لأن النتيجة هي أساسًا عبارة عن قطعة مقطوعة تم إنشاؤها من Derived.

dynamic_cast

  • يستخدم dynamic_cast معلومات نوع وقت التشغيل لمعرفة ما إذا كان الإرسال صالحًا. على سبيل المثال ، قد تفشل (Base*) إلى (Derived*) إذا لم يكن المؤشر بالفعل من النوع المشتق.
  • هذا يعني ، dynamic_cast مكلفة للغاية مقارنة static_cast!
  • بالنسبة لـ A* to B* ، إذا كانت cast is invalid ، فسيعرض dynamic_cast nullptr.
  • بالنسبة إلى A& B& if cast is invalid ، فإن dynamic_cast سيؤدي إلى استثناء bad_cast.
  • على عكس غيرها من يلقي ، هناك وقت التشغيل.

const_cast

  • في حين أن static_cast يمكن أن تفعل غير const لل ​​const لا يمكن أن تذهب في الاتجاه الآخر. يمكن أن تفعل const_cast في كلا الاتجاهين.
  • أحد الأمثلة حيث يأتي هذا مفيد هو التكرار من خلال بعض الحاويات مثل set<T> التي ترجع فقط عناصرها إلى const للتأكد من عدم تغيير مفتاحها. ومع ذلك ، إذا كان هدفك هو تعديل أعضاء غير أساسيين في الكائن ، فيجب أن يكون الأمر على ما يرام. يمكنك استخدام const_cast لإزالة constness.
  • مثال آخر هو عندما تريد تنفيذ T& foo() بالإضافة إلى const T& foo() . لتجنب تكرار التعليمات البرمجية ، يمكنك تطبيق const_cast لإرجاع قيمة دالة واحدة من آخر.

reinterpret_cast

  • هذا يقول أساسا أن تأخذ هذه البايتات في هذا الموقع الذاكرة والتفكير في الأمر على أنه كائن معين.
  • على سبيل المثال ، يمكنك تحميل 4 بايت من عدد عشري إلى 4 بايت من int لمعرفة كيف تبدو وحدات البت في الطفو.
  • من الواضح ، إذا كانت البيانات غير صحيحة للنوع ، فقد تحصل على segfault.
  • لا يوجد تحميل وقت التشغيل لهذا الإرسال.

هل this يجيب على سؤالك؟

لم أستخدم أبداً reinterpret_cast ، وأتساءل ما إذا كان الدخول في حالة يحتاج إليها ليس رائعاً للتصميم السيئ. في قاعدة الكود أنا أعمل على dynamic_cast يستخدم كثيرا. الفرق مع static_cast هو أن dynamic_cast يقوم بتدقيق وقت التشغيل والذي قد يكون (أكثر أماناً) أو قد لا يكون (المزيد من الحمل) هو ما تريده (انظر msdn ).


static_cast هو أول فريق يجب أن تحاول استخدامه. يقوم بأشياء مثل التحويلات الضمنية بين الأنواع (مثل int إلى float أو المؤشر إلى void* ) ، كما يمكنه استدعاء وظائف التحويل الصريحة (أو الضمنية). في العديد من الحالات ، لا يكون من الضروري ذكر static_cast بوضوح ، ولكن من المهم ملاحظة أن بناء الجملة T(something) يعادل (T)something ويجب تجنبه (أكثر من ذلك لاحقًا). A T(something, something_else) آمن ، ومع ذلك ، ومضمون استدعاء منشئ.

static_cast يمكن أيضا أن يلقي من خلال التسلسلات الهرمية الميراث. لا لزوم لها عند الصعود (نحو طبقة أساسية) ، ولكن عند الاستطراد لأسفل ، يمكن استخدامه طالما أنه لا يلقي من خلال الميراث virtual . ومع ذلك ، فإنه لا يتحقق ، وهو سلوك غير محدد إلى static_cast أسفل التسلسل الهرمي إلى نوع ليس في الواقع نوع الكائن.

يمكن استخدام const_cast لإزالة أو إضافة const إلى متغير؛ لا يوجد طاقم C ++ آخر قادر على إزالته (ولا حتى reinterpret_cast ). من المهم ملاحظة أن تعديل قيمة const سابقًا لا يتم تعريفه إلا إذا كان المتغير الأصلي هو const ؛ إذا كنت تستخدمه لإخراج const من إشارة إلى شيء لم يتم الإعلان عنه مع const ، فهو آمن. هذا يمكن أن يكون مفيدا عند تحميل وظائف الأعضاء على أساس const ، على سبيل المثال. يمكن أيضًا استخدامه لإضافة const إلى كائن ، مثل استدعاء تحميل الدالة عضو.

كما يعمل const_cast بشكل مماثل على volatile ، على الرغم من أن هذا أقل شيوعًا.

يستخدم dynamic_cast وجه الحصر تقريبا للتعامل مع تعدد الأشكال. يمكنك إرسال مؤشر أو مرجع إلى أي نوع متعدد الأشكال إلى أي نوع من أنواع الفصول الأخرى (النوع متعدد الأشكال له وظيفة ظاهرية واحدة على الأقل ، معلنة أو موروثة). يمكنك استخدامه لأكثر من مجرد الضغط على الأسفل - يمكنك الإلقاء جانبيًا أو حتى سلسلة أخرى. سوف يبحث dynamic_cast عن الكائن المطلوب وإعادته إن أمكن. إذا لم تستطع ، ستقوم بإرجاع nullptr في حالة المؤشر ، أو رمي std::bad_cast في حالة مرجع.

dynamic_cast لديه بعض القيود ، على الرغم من. لا يعمل إذا كانت هناك كائنات متعددة من نفس النوع في التسلسل الهرمي للميراث (ما يسمى بـ "الماس اللعين") ولا تستخدم الميراث virtual . كما يمكن أن يمر عبر الميراث العام فقط - وسوف يفشل دائمًا في السفر عبر الوراثة protected أو private . لكن هذا نادرًا ما يكون مشكلة ، حيث أن مثل هذه الأشكال من الميراث نادرة.

reinterpret_cast هو ألقى الزهر الأكثر خطورة ، ويجب استخدامه بشكل كبير. يتحول نوع واحد مباشرة إلى آخر - مثل إسقاط القيمة من مؤشر إلى آخر أو تخزين مؤشر في int أو كافة أنواع الأشياء السيئة الأخرى. إلى حد كبير ، يكون الضمان الوحيد الذي تحصل عليه مع reinterpret_cast هو أنه عادةً إذا قمت بإرجاع النتيجة إلى النوع الأصلي ، ستحصل على نفس القيمة بالضبط (ولكن ليس إذا كان النوع الوسيط أصغر من النوع الأصلي). هناك عدد من التحويلات التي لا يمكن لـ reinterpret_cast فعلها أيضًا. يتم استخدامه بشكل أساسي للتحويلات الغريبة والتلاعبات في البتات ، مثل تحويل مسار البيانات الخام إلى بيانات فعلية ، أو تخزين البيانات في البتات المنخفضة لمؤشر محاذاة.

يتم إلقاء المصبوب على النمط C والطراز الوظيفي على شكل (type)object أو type(object) ، على التوالي. يتم تعريف نمط C على أنه الأول من التالي الذي ينجح:

  • const_cast
  • static_cast (على الرغم من تجاهل قيود الوصول)
  • static_cast (انظر أعلاه) ، ثم const_cast
  • reinterpret_cast
  • reinterpret_cast ، ثم const_cast

وبالتالي يمكن استخدامه كبديل للفئات الأخرى في بعض الحالات ، ولكن يمكن أن يكون خطيراً للغاية بسبب القدرة على الانتقال إلى reinterpret_cast ، ويجب تفضيل هذا الأخير عند الحاجة إلى صريح ، إلا إذا كنت متأكداً من نجاح static_cast أو سوف يفشل reinterpret_cast . حتى ذلك الحين ، ضع في اعتبارك الخيار الأطول والأكثر صراحة.

تتجاهل أنماط C النمطية أيضًا التحكم في الوصول عند تنفيذ static_cast ، مما يعني أن لديهم القدرة على إجراء عملية لا يمكن لأي مجموعة أخرى. هذا هو في الغالب kludge ، رغم ذلك ، وفي رأيي هو مجرد سبب آخر لتجنب C- نمط يلقي.





c++-faq