الميراث المتعدد:نتيجة غير متوقعة بعد التحويل من الفراغ * إلى الفئة الأساسية الثانية

StackOverflow https://stackoverflow.com/questions/2379427

سؤال

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

لذلك قمت بإنشاء حالة اختبار صغيرة، البيئة هي gcc 4.2 على نظام التشغيل Mac OS X:

class Shape {
public:
    virtual int w() = 0;
    virtual int h() = 0;
};

class Square : public Shape {
public:
    int l;
    int w() {return l;}
    int h() {return l;}
};

class Decorated {
public:
    int padding;
    int w() {return 2*padding;}
    int h() {return 2*padding;}
};

class DecoratedSquare : public Square, public Decorated {
public:
    int w() {return Square::w() + Decorated::w();}
    int h() {return Square::h() + Decorated::h();}
};


#include <iostream>

template <class T> T shape_cast(void *vp) {
//    return dynamic_cast<T>(vp);   // not possible, no pointer to class type
//    return static_cast<T>(vp);
//    return T(vp);
//    return (T)vp;
    return reinterpret_cast<T>(vp);
}

int main(int argc, char *argv[]) {
    DecoratedSquare *ds = new DecoratedSquare;
    ds->l = 20;
    ds->padding = 5;
    void *dsvp = ds;

    std::cout << "Decorated (direct)" << ds->w() << "," << ds->h() << std::endl;

    std::cout << "Shape " << shape_cast<Shape*>(dsvp)->w() << "," << shape_cast<Shape*>(dsvp)->h() << std::endl;
    std::cout << "Square " << shape_cast<Square*>(dsvp)->w() << "," << shape_cast<Square*>(dsvp)->h() << std::endl;
    std::cout << "Decorated (per void*) " << shape_cast<Decorated*>(dsvp)->w() << "," << shape_cast<Decorated*>(dsvp)->h() << std::endl;
    std::cout << "DecoratedSquare " << shape_cast<DecoratedSquare*>(dsvp)->w() << "," << shape_cast<DecoratedSquare*>(dsvp)->h() << std::endl;
}

ينتج الإخراج التالي:

Decorated (direct)30,30
Shape 30,30
Square 30,30
Decorated (per void*) 73952,73952
DecoratedSquare 30,30

كما ترى، فإن نتيجة "المزخرفة (لكل فراغ*)" خاطئة تمامًا.ويجب أن يكون أيضًا 30,30 كما في السطر الأول.

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

من خلال فهمي لـ C++، يجب أن يعمل هذا بالفعل.هل هناك أي فرصة لجعل هذا يعمل مع الفراغ*؟هل يمكن أن يكون هذا خطأ في دول مجلس التعاون الخليجي؟

شكرًا

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

المحلول

إنه ليس حشرة البرمجيات - هذا ما reinterpret_cast يفعل. ال DecoratedSquare سيتم وضع الكائن في الذاكرة شيء مثل هذا:

Square
Decorated
DecoratedSquare specific stuff

تحويل مؤشر إلى هذا void* سيعطي عنوان بداية هذه البيانات ، مع عدم معرفة النوع الموجود. reinterpret_cast<Decorated*> سوف يأخذ هذا العنوان وتفسير كل ما هو موجود ك Decorated - لكن محتويات الذاكرة الفعلية هي Square. هذا خطأ ، لذلك تحصل على سلوك غير محدد.

يجب أن تحصل على النتائج الصحيحة إذا كنت reinterpret_cast إلى النوع الديناميكي الصحيح (أي DecoratedSquare) ، ثم تحويل إلى فئة قاعدة.

نصائح أخرى

كرر عشر مرات - الشيء الوحيد الذي يمكنك القيام به بأمان مع أ reinterpret_cast المؤشر reinterpret_cast عاد إلى نفس نوع المؤشر الذي جاء منه. الأمر نفسه ينطبق على التحويلات إلى void*: يجب تحويل مرة أخرى إلى النوع الأصلي.

لذا ، إذا ألقيت ملف DecoratedSquare* إلى void*, ، يجب أن تعيدها إلى DecoratedSquare*. لا Decorated*, ، ليس Square*, ، ليس Shape*. قد يعمل بعضهم على جهازك ، ولكن هذا مزيج من الحظ السعيد والسلوك الخاص بالتنفيذ. عادةً ما يعمل مع فردي ، لأنه لا يوجد سبب واضح لتنفيذ مؤشرات الكائنات بطريقة تمنعها من العمل ، ولكن هذا غير مضمون ، ولا يمكن أن يعمل بشكل عام للميراث المتعدد.

أنت تقول أن الكود الخاص بك يصل إلى "أنواع تعسفية ، بما في ذلك الأنواع البدائية" عبر الفراغ*. لا حرج في هذا - من المفترض أن كل من يتلقى البيانات يعرف أن يعاملها على أنها أ DecoratedSquare* وليس كما قل ، int*.

إذا كان من يتلقى ذلك يعرف فقط أن يعامله كطبقة أساسية ، مثل Decorated*, ، ثم من يحولها إلى void* ينبغي static_cast إلى الفئة الأساسية أولاً ، ثم إلى void*:

void *decorated_vp = static_cast<Decorated*>(ds);

الآن عندما تلمح decorated_vp ارجع الى Decorated*, ، ستحصل على نتيجة static_cast<Decorated*>(ds), وهو ما تحتاجه.

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

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