سؤال

لنفترض أن لدي 3 فئات على النحو التالي (لأن هذا مثال ، لن يتم تجميعه!):

class Base
{
public:
   Base(){}
   virtual ~Base(){}
   virtual void DoSomething() = 0;
   virtual void DoSomethingElse() = 0;
};

class Derived1
{
public:
   Derived1(){}
   virtual ~Derived1(){}
   virtual void DoSomething(){ ... }
   virtual void DoSomethingElse(){ ... }
   virtual void SpecialD1DoSomething{ ... }
};

class Derived2
{
public:
   Derived2(){}
   virtual ~Derived2(){}
   virtual void DoSomething(){ ... }
   virtual void DoSomethingElse(){ ... }
   virtual void SpecialD2DoSomething{ ... }
};

أرغب في إنشاء مثيل من Derived1 أو Derived2 اعتمادًا على بعض الإعدادات غير المتوفرة حتى وقت التشغيل.

بما أنني لا أستطيع تحديد النوع المشتق حتى وقت التشغيل ، فهل تعتقد أن ما يلي ممارسة سيئة؟ ...

class X
{
public:
   ....

   void GetConfigurationValue()
   {
      ....
      // Get configuration setting, I need a "Derived1"
      b = new Derived1();

      // Now I want to call the special DoSomething for Derived1
      (dynamic_cast<Derived1*>(b))->SpecialD1DoSomething();      
   }
private:
   Base* b;
};

لقد قرأت عمومًا أن استخدام Dynamic_cast سيء ، لكن كما قلت ، لا أعرف أي نوع يجب إنشاءه حتى وقت التشغيل. الرجاء المساعدة!

هل كانت مفيدة؟

المحلول

استخدام Dynamic_cast ليس ممارسة سيئة في حد ذاتها. إنها ممارسة سيئة لاستخدامها بشكل غير لائق ، أي حيث لا تكون هناك حاجة إليها حقًا.

إنها أيضًا ممارسة سيئة لاستخدامها بهذه الطريقة:

(dynamic_cast<Derived1*>(b))->SpecialD1DoSomething();  

السبب: Dynamic_cast (ب) قد يعود فارغة.

عند استخدام Dynamic_cast ، يجب أن تكون حذراً للغاية ، لأنه غير مضمون ، أن B هو في الواقع من النوع المشتق 1 وليس مشتقًا 2:

void GenericFunction(Base* p)
{
    (dynamic_cast<Derived1*>(b))->SpecialD1DoSomething();
}

void InitiallyImplementedFunction()
{
   Derived1 d1;
   GenericFunction(&d1); // OK... But not for long. 
   // Especially, if implementation of GenericFunction is in another library
   // with not source code available to even see its implementation 
   // -- just headers
}    

void SomeOtherFunctionProbablyInAnotherUnitOfCompilation()
{
   Derived2 d2;
   GenericFunction(&d2); // oops!
}

يجب عليك التحقق مما إذا كان Dynamic_cast ناجحًا بالفعل. هناك طريقتان للقيام بذلك: التحقق من ذلك قبل وبعد الممثلين. قبل أن تتمكن من التحقق مما إذا كان المؤشر الذي تحاول إلقاؤه هو في الواقع هو الذي تتوقعه عبر RTTI:

if (typeid(b) == typeid(Derived1*))
{
   // in this case it's safe to call the function right 
   // away without additional checks
   dynamic_cast<Derived1*>(b)->SpecialD1DoSomething();
}
else
{
  // do something else, like try to cast to Derived2 and then call
  // Derived2::SpecialD2DoSomething() in a similar fashion
}

إن التحقق من ذلك هو في الواقع أبسط قليلاً:

Derived1* d1 = dynamic_cast<Derived1*>(b);
if (d1 != NULL)
{
   d1->SpecialD1DoSomething();
}

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

نصائح أخرى

لماذا لا تؤخر اللحظة التي "ترفرف فيها" بعضًا إذا كان نوع المعلومات عن طريق تعيين مؤشر لمؤشر إلى قاعدة:

void GetConfigurationValue()
{
  // ...
  // Get configuration setting, I need a "Derived1"
  Derived1* d1 = new Derived1();
  b = d1;

  // Now I want to call the special DoSomething for Derived1
  d1->SpecialD1DoSomething();
}

نقطة الوظائف الافتراضية هي أنه بمجرد أن يكون لديك النوع الصحيح من الكائن ، يمكنك استدعاء الوظيفة الصحيحة بدون معرفة الفئة المستمدة من هذا الكائن - يمكنك فقط استدعاء الوظيفة الافتراضية ، وهو يفعل الشيء الصحيح.

تحتاج فقط dynamic_cast عندما يكون لديك فئة مشتقة تحدد شيئًا مختلفًا ليس موجود في الفئة الأساسية ، و تحتاج/تريد أن تأخذ شيء إضافي في الاعتبار.

فمثلا:

struct Base { 
    virtual void do_something() {}
};

struct Derived : Base { 
    virtual void do_something() {} // override dosomething
    virtual void do_something_else() {} // add a new function
};

الآن ، إذا كنت تريد الاتصال فقط do_something(), ، أ dynamic_cast غير ضروري تماما. على سبيل المثال ، يمكنك الحصول على مجموعة من Base *, ، واستدعاء فقط do_something() على كل واحد ، دون إيلاء أي اهتمام لما إذا كان الكائن حقًا Base أو أ Derived.

متى/إذا كان لديك ملف Base *, ، وتريد الاحتجاج do_something_else(), ومن بعد يمكنك استخدام أ dynamic_cast لمعرفة ما إذا كان الكائن نفسه هو حقا Derived لذلك يمكنك استدعاء ذلك.

بعض الأشياء الأخرى التي قد ترغب في التفكير فيها لتجنب استخدام Dynamic_cast

من C ++ الفعال (الإصدار الثالث) - البند 35 بدائل للوظائف الافتراضية -

  1. "نمط طريقة القالب" عبر الواجهة غير الحية (NVI). إن جعل الوظائف الافتراضية خاصة/محمية بأسلوب عام "Wrapper" - يتيح لك فرض بعض سير العمل الأخرى للأشياء التي يجب القيام بها قبل وبعد الطريقة الافتراضية.
  2. "نمط الاستراتيجية" عبر مؤشرات الوظيفة. تمرير في الطريقة الإضافية كمؤشر وظيفة.
  3. "نمط الاستراتيجية" عبر وظيفة TR1 ::. على غرار 2. ولكن يمكنك تزويد فصول كاملة بخيارات مختلفة
  4. "نمط الاستراتيجية" الكلاسيكية. استراتيجية منفصلة من الطبقة الرئيسية - ادفع الوظائف الافتراضية إلى التسلسل الهرمي الآخر.

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

يتمتع!

ما الخطا في:

Base * b;
if( some_condition ) {
   b = new Derived1;
}
else {
   b = new Derived2;
}

if ( Derived2 * d2 = dynamic_cast <Derived2 *>( b ) ) {
    d2->SpecialD2DoSomething();
}

أم هل فاتني شيء؟

وهل يمكن أن يقترح OI أنه عند نشر أسئلة مثل هذه ، يمكنك تسمية الفصول الدراسية A و B و C وما إلى ذلك ووظائفك مثل F1 () و F2 () وما إلى ذلك. .

طريقة واحدة لتجنب dynamic_cast هو أن يكون لديك وظيفة الترامبولين الافتراضية "شيء خاص" الذي يستدعي تنفيذ الأشكال المشتقة تلك الفئة المشتقة "الخاصة بـ" specialDxdosomething () والتي يمكن أن تكون أي اسم فئة غير قاعدية تريدها. يمكن أن تستدعي أكثر من وظيفة واحدة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top