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




pointers casting c++-faq (7)

(وقد قدم الكثير من التفسير النظري والمفاهيمي أعلاه)

فيما يلي بعض الأمثلة العملية عند استخدام static_cast ، dynamic_cast ، const_cast ، reinterpret_cast .

(يشير هذا أيضًا إلى فهم التفسير: http://www.cplusplus.com/doc/tutorial/typecasting/ )

static_cast:

OnEventData(void* pData)

{
  ......

  //  pData is a void* pData, 

  //  EventData is a structure e.g. 
  //  typedef struct _EventData {
  //  std::string id;
  //  std:: string remote_id;
  //  } EventData;

  // On Some Situation a void pointer *pData
  // has been static_casted as 
  // EventData* pointer 

  EventData *evtdata = static_cast<EventData*>(pData);
  .....
}

dynamic_cast:

void DebugLog::OnMessage(Message *msg)
{
    static DebugMsgData *debug;
    static XYZMsgData *xyz;

    if(debug = dynamic_cast<DebugMsgData*>(msg->pdata)){
        // debug message
    }
    else if(xyz = dynamic_cast<XYZMsgData*>(msg->pdata)){
        // xyz message
    }
    else/* if( ... )*/{
        // ...
    }
}

const_cast:

// *Passwd declared as a const

const unsigned char *Passwd


// on some situation it require to remove its constness

const_cast<unsigned char*>(Passwd)

reinterpret_cast:

typedef unsigned short uint16;

// Read Bytes returns that 2 bytes got read. 

bool ByteBuffer::ReadUInt16(uint16& val) {
  return ReadBytes(reinterpret_cast<char*>(&val), 2);
}

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

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

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


في حين أن إجابات أخرى وصفت جميع الاختلافات بين C ++ ، أود إضافة ملاحظة قصيرة لماذا لا ينبغي عليك استخدام نماذج C النمط (Type) var Type(var) .

بالنسبة للمبتدئين C ++ ، تبدو أنماط C-style وكأنها العملية الفائقة من خلال C + cs (static_cast <> () و dynamic_cast <> () و const_cast <> () و reinterpret_cast <> () وأن شخصًا ما يفضلها على قوالب C ++ . في الحقيقة ، يكون أسلوب C-style هو مجموعة الحروف الفائقة وأقصرها للكتابة.

تكمن المشكلة الرئيسية في نمط C في أنها تخفي النية الحقيقية للمطورين. يمكن للمجموعات النمطية C أن تقوم بكافة أنواع الصب من القوالب الآمنة بشكل طبيعي من قبل static_cast <> () و dynamic_cast <> () إلى الطوائف التي يحتمل أن تكون خطرة مثل const_cast <> () ، حيث يمكن إزالة معدِّل const بحيث تكون المتغيرات const يمكن تعديل و reinterpret_cast <> () التي يمكن حتى إعادة تفسير القيم الصحيحة إلى المؤشرات.

هنا هو العينة.

int a=rand(); // Random number.

int* pa1=reinterpret_cast<int*>(a); // OK. Here developer clearly expressed he wanted to do this potentially dangerous operation.

int* pa2=static_cast<int*>(a); // Compiler error.
int* pa3=dynamic_cast<int*>(a); // Compiler error.

int* pa4=(int*) a; // OK. C-style cast can do such cast. The question is if it was intentional or developer just did some typo.

*pa4=5; // Program crashes.

كان السبب الرئيسي في إضافة نماذج C ++ إلى اللغة هو السماح لمطوّر البرامج بتوضيح نواياه - لماذا سيقوم بذلك. باستخدام C-style casts التي تكون صالحة تمامًا في C ++ فإنك تجعل كودك أقل قابلية للقراءة وأكثر عرضة للخطأ خاصة للمطورين الآخرين الذين لم يقوموا بإنشاء شفرتك. لذا ، لجعل الرمز الخاص بك أكثر قابلية للقراءة وصريحًا ، يجب دائمًا تفضيل C ++ casts على C-style casts.

هنا اقتباس قصير من Bjarne Stroustrup (مؤلف كتاب C ++) The C ++ Programming Language 4th edition - الصفحة 302.

هذا النمط المصمم على النمط C أكثر خطورة بكثير من مُشغلات التحويل المسماة لأنه من الصعب تحديد الترميز في برنامج كبير ونوع التحويل الذي يقصده المبرمج ليس صريحًا.


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

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


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

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

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

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


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- نمط يلقي.


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

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.
  • لا يوجد تحميل وقت التشغيل لهذا الإرسال.

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

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







c++ pointers casting c++-faq