سؤال

هل هناك على أي حال أن يكون لديك نوع من الأعضاء الثابت الظاهري في C++؟

على سبيل المثال:

class BaseClass {
    public:
        BaseClass(const string& name) : _name(name) {}
        string GetName() const { return _name; }
        virtual void UseClass() = 0;
    private:
        const string _name;
};


class DerivedClass : public BaseClass {
    public:
        DerivedClass() : BaseClass("DerivedClass") {}
        virtual void UseClass() { /* do something */ }
};

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

class BaseClass {
    public:
        BaseClass() {}
        virtual string GetName() const = 0;
        virtual void UseClass() = 0;
};


class DerivedClass : public BaseClass {
    public:
        DerivedClass() {}
        virtual string GetName() const { return _name; }
        virtual void UseClass() { /* do something */ }
    private:
        static const string _name;
};

string DerivedClass::_name = "DerivedClass";

هذا الحل لا يرضيني لأنني بحاجة إلى إعادة تنفيذ اسم العضو وموصله GetName() في كل فصل.في حالتي لدي العديد من الأعضاء الذين يتبعون سلوك _name وأعشار الفئات المشتقة.

اي فكرة؟

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

المحلول

هنا حل واحد:

struct BaseData
{
  const string my_word;
  const int my_number;
};

class Base
{
public:
    Base(const BaseData* apBaseData)
    {
        mpBaseData = apBaseData;
    }
    const string getMyWord()
    {
        return mpBaseData->my_word;
    }
    int getMyNumber()
    {
        return mpBaseData->my_number;
    }
private:
    const BaseData* mpBaseData;
};

class Derived : public Base
{
public:
    Derived() : Base(&sBaseData)
    {
    }
private:
    static BaseData sBaseData;
}

BaseData Derived::BaseData = { "Foo", 42 };

نصائح أخرى

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

إذا كنت تصر على تنفيذ الأعضاء "المشتركين" كأعضاء ثابتين في الفئة المشتقة، فقد تتمكن من إنشاء كود الفئات المشتقة تلقائيًا.تعد XSLT أداة رائعة لإنشاء فئات بسيطة تلقائيًا.

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

في الختام - لغرض المثال الأصلي، ليست هناك حاجة لمثل هؤلاء الأعضاء "الثابتين الافتراضيين".إذا كنت لا تزال تعتقد أنها ضرورية للكود الذي تكتبه، فيرجى محاولة توضيح وإضافة المزيد من السياق.

مثال على ما وصفته أعلاه:

class BaseClass {
    public:
        BaseClass(const Descriptor& desc) : _desc(desc) {}
        string GetName() const { return _desc.name; }
        int GetId() const { return _desc.Id; }
        X GetX() connst { return _desc.X; }
        virtual void UseClass() = 0;
    private:
        const Descriptor _desc;
};


class DerivedClass : public BaseClass {
    public:
        DerivedClass() : BaseClass(Descriptor("abc", 1,...)) {}
        virtual void UseClass() { /* do something */ }
};

class DerDerClass : public BaseClass {
    public:
        DerivedClass() : BaseClass("Wowzer", 843,...) {}
        virtual void UseClass() { /* do something */ }
};

أود أن أتناول هذا الحل بالتفصيل، وربما أقدم حلاً لمشكلة إلغاء التهيئة:

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

يمكنك إنشاء كائن مفرد، DescriptorMap، الذي سيحتفظ بمثيل واحد لكل واصف، ويستخدمه عند إنشاء الكائنات المشتقة كما يلي:

enum InstanceType {
    Yellow,
    Big,
    BananaHammoc
}

class DescriptorsMap{
    public:
        static Descriptor* GetDescriptor(InstanceType type) {
            if ( _instance.Get() == null) {
                _instance.reset(new DescriptorsMap());
            }
            return _instance.Get()-> _descriptors[type];
        }
    private:
        DescriptorsMap() {
            descriptors[Yellow] = new Descriptor("Yellow", 42, ...);
            descriptors[Big] = new Descriptor("InJapan", 17, ...)
            ...
        }

        ~DescriptorsMap() {
            /*Delete all the descriptors from the map*/
        }

        static autoptr<DescriptorsMap> _instance;
        map<InstanceType, Descriptor*> _descriptors;
}

الآن يمكننا أن نفعل هذا:

class DerivedClass : public BaseClass {
    public:
        DerivedClass() : BaseClass(DescriptorsMap.GetDescriptor(InstanceType.BananaHammoc)) {}
        virtual void UseClass() { /* do something */ }
};

class DerDerClass : public BaseClass {
    public:
        DerivedClass() : BaseClass(DescriptorsMap.GetDescriptor(InstanceType.Yellow)) {}
        virtual void UseClass() { /* do something */ }
};

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

إذن لدينا الآن مثيل واحد لكل واصف يتم حذفه أيضًا في نهاية التنفيذ.

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

أتفق مع اقتراح هيرشي باستخدام القالب باعتباره "الفئة الأساسية".مما تصفه، يبدو الأمر أشبه باستخدام القوالب بدلاً من التصنيف الفرعي.

يمكنك إنشاء قالب على النحو التالي (لم تحاول تجميع هذا):


template <typename T>
class Object
{
public:

  Object( const T& newObject ) : yourObject(newObject) {} ;
  T GetObject() const { return yourObject } ;
  void SetObject( const T& newObject ) { yourObject = newObject } ;

protected:

  const T yourObject ;
} ;

class SomeClassOne
{
public:

  SomeClassOne( const std::vector& someData )
  {
    yourData.SetObject( someData ) ;
  }

private:

  Object<std::vector<int>> yourData ;
} ;

سيتيح لك ذلك استخدام أساليب فئة القالب لتعديل البيانات حسب الحاجة من داخل فئاتك المخصصة التي تستخدم البيانات ومشاركة الجوانب المختلفة لفئة القالب.

إذا كنت تنوي استخدام الميراث، فقد يتعين عليك اللجوء إلى "متعة" استخدام مؤشر void* في BaseClass الخاص بك والتعامل مع عملية الإرسال، وما إلى ذلك.

ومع ذلك، بناءً على شرحك، يبدو أنك بحاجة إلى قوالب وليس وراثة.

@هيرشي:المشكلة في هذا الأسلوب هي أن كل مثيل لكل فئة مشتقة لديه نسخة من البيانات، والتي قد تكون باهظة الثمن بطريقة ما.

ربما يمكنك تجربة شيء كهذا (أنا أتحدث بدون مثال تجميعي، لكن الفكرة يجب أن تكون واضحة).


#include <iostream>
#include <string>
using namespace std;

struct DerivedData
{
  DerivedData(const string & word, const int number) :
    my_word(word), my_number(number) {}
  const string my_word;
  const int my_number;
};

class Base {
public:
  Base() : m_data(0) {}
  string getWord() const { return m_data->my_word; }
  int getNumber() const { return m_data->my_number; }
protected:
  DerivedData * m_data;
};


class Derived : public Base {
public:
  Derived() : Base() {
    if(Derived::s_data == 0) {
      Derived::s_data = new DerivedData("abc", 1);
    }
    m_data = s_data;
  }
private:
  static DerivedData * s_data;
};


DerivedData * Derived::s_data = 0; 

int main()
{
  Base * p_b = new Derived();
  cout getWord() << endl;
}

فيما يتعلق بسؤال المتابعة حول حذف الكائن الثابت:الحل الوحيد الذي يتبادر إلى الذهن هو استخدام مؤشر ذكي، شيء من هذا القبيل تعزيز المؤشر المشترك.

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

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