سؤال

وقد طلب هذا في المقابلة.

كيفية كتابة Dynamic_cast. أعتقد ، على أساس وظيفة اسم typeid.

الآن كيف تنفذ الطباعة الخاصة؟ ليس لدي أدنى فكرة عنه.

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

المحلول

هناك سبب ليس لديك أي فكرة ، dynamic_cast و static_cast ليسوا مثل const_cast أو reinterpret_cast, ، يقومون بالفعل بأداء حساب مؤشر وذات نوع إلى حد ما.

مؤشر الحساب

من أجل توضيح ذلك ، فكر في التصميم التالي:

struct Base1 { virtual ~Base1(); char a; };
struct Base2 { virtual ~Base2(); char b; };

struct Derived: Base1, Base2 {};

مثال Derived يجب أن تبدو مثل هذا (يعتمد على مجلس التعاون الخليجي لأنه يعتمد بالفعل على المترجم ...):

|      Cell 1       | Cell 2 |      Cell 3        | Cell 4 |
| vtable pointer    |    a   | vtable pointer     |    b   |
|         Base 1             |        Base 2               |
|                     Derived                              |

فكر الآن في العمل اللازم للالتصاب:

  • صب من Derived إلى Base1 لا يتطلب أي عمل إضافي ، فهي في نفس العنوان المادي
  • صب من Derived إلى Base2 يستلزم تحويل المؤشر بمقدار 2 بايت

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

في الكود ، هذا سوف يترجم مثل:

Derived derived;
Base2* b2 = reinterpret_cast<Base2>(((char*)&derived) + 2);

وهذا بالطبع ل static_cast.

الآن ، إذا تمكنت من استخدامها static_cast في تنفيذ dynamic_cast, ، ثم يمكنك الاستفادة من برنامج التحويل البرمجي والسماح له بالتعامل مع حساب المؤشر بالنسبة لك ... لكنك لا تزال غير خارج الخشب.

كتابة Dynamic_cast؟

أول الأشياء أولاً ، نحتاج إلى توضيح مواصفات dynamic_cast:

  • dynamic_cast<Derived*>(&base); يعود فارغة إذا base ليس مثالا على Derived.
  • dynamic_cast<Derived&>(base); رميات std::bad_cast في هذه الحالة.
  • dynamic_cast<void*>(base); يعيد عنوان الفئة الأكثر اشتقاقًا
  • dynamic_cast احترام مواصفات الوصول (public, protected و private ميراث)

لا أعرف عنك ، لكنني أعتقد أنه سيكون قبيحًا. استخدام typeid لا يكفي هنا:

struct Base { virtual ~Base(); };
struct Intermediate: Base {};
struct Derived: Base {};

void func()
{
  Derived derived;
  Base& base = derived;
  Intermediate& inter = dynamic_cast<Intermediate&>(base); // arg
}

القضية هنا هي ذلك typeid(base) == typeid(Derived) != typeid(Intermediate), ، لذلك لا يمكنك الاعتماد على ذلك أيضًا.

شيء آخر مسلية:

struct Base { virtual ~Base(); };
struct Derived: virtual Base {};

void func(Base& base)
{
  Derived& derived = static_cast<Derived&>(base); // Fails
}

static_cast لا يعمل عندما يكون الوراثة الافتراضية متورطة ... لذلك نواجه مشكلة في حساب الحساب المؤشر.

حل تقريبا

class Object
{
public:
  Object(): mMostDerived(0) {}
  virtual ~Object() {}

  void* GetMostDerived() const { return mMostDerived; }

  template <class T>
  T* dynamiccast()
  {
    Object const& me = *this;
    return const_cast<T*>(me.dynamiccast());
  }

  template <class T>
  T const* dynamiccast() const
  {
    char const* name = typeid(T).name();
    derived_t::const_iterator it = mDeriveds.find(name);
    if (it == mDeriveds.end()) { return 0; }
    else { return reinterpret_cast<T const*>(it->second); }
  }

protected:
  template <class T>
  void add(T* t)
  {
    void* address = t;
    mDerived[typeid(t).name()] = address;
    if (mMostDerived == 0 || mMostDerived > address) { mMostDerived= address; }
  }

private:
  typedef std::map < char const*, void* > derived_t;
  void* mMostDerived;
  derived_t mDeriveds;
};

// Purposely no doing anything to help swapping...

template <class T>
T* dynamiccast(Object* o) { return o ? o->dynamiccast<T>() : 0; }

template <class T>
T const* dynamiccast(Object const* o) { return o ? o->dynamiccast<T>() : 0; }

template <class T>
T& dynamiccast(Object& o)
{
  if (T* t = o.dynamiccast<T>()) { return t; }
  else { throw std::bad_cast(); }
}

template <class T>
T const& dynamiccast(Object const& o)
{
  if (T const* t = o.dynamiccast<T>()) { return t; }
  else { throw std::bad_cast(); }
}

أنت بحاجة إلى بعض الأشياء الصغيرة في المنشئ:

class Base: public Object
{
public:
  Base() { this->add(this); }
};

لذلك ، دعنا نتحقق:

  • الاستخدامات الكلاسيكية: حسنًا
  • virtual ميراث ؟ يجب أن تعمل ... ولكن لم يتم اختبارها
  • احترام مواصفات الوصول ... arg:/

حظا سعيدا لأي شخص يحاول تنفيذ هذا خارج المترجم ، حقا: x

نصائح أخرى

طريقة واحدة هي إعلان معرف ثابت (عدد صحيح على سبيل المثال) الذي يحدد معرف الفصل. في الفصل ، يمكنك تنفيذ كل من الروتينات الثابتة والمخططة التي تُرجع معرف الفصل (تذكر أن تميز الروتين الظاهري).

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

يمكن أن يكون التنفيذ العادل فئة قالب:

template <typename T>
class ClassId<T>
{
    public:

    static int GetClassId() { return (sClassId); }

    virtual int GetClassId() const { return (sClassId); }

    template<typename U> static void StateDerivation() {
        gClassMap[ClassId<T>::GetClassId()].push_back(ClassId<U>::GetClassId());
    }

    template<typename U> const U DynamicCast() const {
        std::map<int, std::list<int>>::const_iterator it = gClassMap.find(ClassId<T>); // Base class type, with relative derivations declared with StateDerivation()
        int id = ClassId<U>::GetClassId();

        if (id == ClassId<T>::GetClassId()) return (static_cast<U>(this));

        while (it != gClassMap.end()) {
            for (std::list<int>::const_iterator = pit->second.begin(), pite = it->second->end(); pit != pite; pit++) {
                if ((*pit) == id) return (static_cast<U>(this));
                // ... For each derived element, iterate over the stated derivations.
                // Easy to implement with a recursive function, better if using a std::stack to avoid recursion.
            }
        }

        return (null); 
    }  

    private:

    static int sClassId;
}

#define CLASS_IMP(klass) static int ClassId<klass>::sClassId = gClassId++;

// Global scope variables    

static int gClassId = 0;
static std::map<int, std::list<int>> gClassMap;

يجب تعريف class_imp في كل فئة مستمدة من ClassID ، ويكون GClassid و GclassMap مرئيًا في النطاق العالمي.

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

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

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


يجب تعريف العلاقات بين الفصول يدويًا ، متشددين مع روتين التهيئة. هذا يسمح بتحديد ما إذا كانت فئة مستمدة من فئتين كاشتقاق شائع.

class Base : ClassId<Base> {  }
#define CLASS_IMP(Base);
class Derived : public Base, public ClassId<Derived> {  }
#define CLASS_IMP(Derived);
class DerivedDerived : public Derived, public ClassId<DerivedDerived> {  }
#define CLASS_IMP(DerivedDerived);

static void DeclareDerivations()
{
    ClassId<Base>::StateDerivation<Derived>();
    ClassId<Derived>::StateDerivation<DerivedDerived>();
}

أنا شخصياً أتفق مع "هناك سبب إذا قام المترجمون بتنفيذ Dynamic_cast" ؛ ربما يقوم المترجم بالأشياء بشكل أفضل (خاصة فيما يتعلق برمز المثال!).

لمحاولة إجابة أقل روتينية قليلاً ، إذا كان أقل تعريفًا قليلاً:

ما عليك القيام به هو إلقاء المؤشر إلى int*، وإنشاء نوع جديد T على المكدس ، وإلقاء مؤشر إليه إلى int*، ومقارنة int الأولى في كلا النوعين. هذا سوف يفعل مقارنة العنوان VTABLE. إذا كانت نفس النوع ، فسيكون لديهم نفس VTABLE. آخر ، لا يفعلون ذلك.

كلما كان أكثر عقلانية منا مجرد استمرار ثابت في فصولنا.

سهل. اشتق جميع الكائنات من بعض واجهة typeid مع وظيفة افتراضية wwami (). تجاوزها في جميع الفصول المشتقة.

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