سؤال

يبدو أنه كان لدي سوء فهم أساسي حول C++ :<

أنا أحب حل الحاوية متعدد الأشكال.شكرًا لك على لفت انتباهي إلى ذلك :)


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

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

أخيرًا، نظرًا لأنني أريد القيام بواحدة أو أخرى، فإنني أفضل تعدد الأشكال.أجد أنه من الأسهل بكثير تمثيل القيود مثل "تحتوي هذه الحاوية على أنواع قابلة للمقارنة" - على غرار Java.

أدخلني في موضوع السؤال :في أكثر الأمور تجريدية، أتخيل أنه يمكنني الحصول على واجهة افتراضية خالصة "حاوية" تحتوي على شيء يشبه "push(void* data) and pop(void* data)" (للتسجيل، أنا لا أحاول فعليًا التنفيذ كومة).

ومع ذلك، لا أحب حقًا الفراغ* في المستوى الأعلى، ناهيك عن أن التوقيع سيتغير في كل مرة أرغب في إضافة قيد على نوع البيانات التي يمكن لحاوية محددة العمل معها.

تلخيص:لدينا حاويات معقدة نسبيًا لها طرق مختلفة لاسترداد العناصر.نريد أن نكون قادرين على تغيير القيود المفروضة على العناصر التي يمكن إدخالها في الحاويات.يجب أن تعمل العناصر مع أنواع متعددة من الحاويات (طالما أنها تلبي قيود تلك الحاوية المحددة).

يحرر:يجب أن أذكر أيضًا أن الحاويات نفسها يجب أن تكون متعددة الأشكال.هذا هو السبب الرئيسي لعدم رغبتي في استخدام قالب C++.

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

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

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

المحلول

هل لا يمكن أن يكون لديك فئة حاوية جذر تحتوي على عناصر:

template <typename T>
class Container
{
public: 

   // You'll likely want to use shared_ptr<T> instead.
   virtual void push(T *element) = 0;
   virtual T *pop() = 0;
   virtual void InvokeSomeMethodOnAllItems() = 0;
};

template <typename T>
class List : public Container<T>
{
    iterator begin();
    iterator end();
public:
    virtual void push(T *element) {...}
    virtual T* pop() { ... }
    virtual void InvokeSomeMethodOnAllItems() 
    {
       for(iterator currItem = begin(); currItem != end(); ++currItem)
       {
           T* item = *currItem;
           item->SomeMethod();
       }
    }
};

يمكن بعد ذلك تمرير هذه الحاويات بشكل متعدد الأشكال:

class Item
{
public:
   virtual void SomeMethod() = 0;
};

class ConcreteItem
{
public:
    virtual void SomeMethod() 
    {
        // Do something
    }
};  

void AddItemToContainer(Container<Item> &container, Item *item)
{
   container.push(item);
}

...

List<Item> listInstance;
AddItemToContainer(listInstance, new ConcreteItem());
listInstance.InvokeSomeMethodOnAllItems();

يمنحك هذا واجهة الحاوية بطريقة عامة وآمنة من حيث النوع.

إذا كنت تريد إضافة قيود على نوع العناصر التي يمكن احتواؤها، فيمكنك القيام بشيء مثل هذا:

class Item
{
public:
  virtual void SomeMethod() = 0;
  typedef int CanBeContainedInList;
};

template <typename T>
class List : public Container<T>
{
   typedef typename T::CanBeContainedInList ListGuard;
   // ... as before
};

نصائح أخرى

يمكنك النظر في استخدام حاوية قياسية دفعة::أي إذا كنت تقوم بتخزين بيانات عشوائية حقًا في الحاوية.

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

الشيء البسيط هو تحديد فئة أساسية مجردة تسمى Container, ، وفئتها الفرعية لكل نوع من العناصر التي قد ترغب في تخزينها.ثم يمكنك استخدام أي فئة جمع قياسية (std::vector, std::list, ، وما إلى ذلك) لتخزين المؤشرات Container.ضع في اعتبارك أنه نظرًا لأنك ستقوم بتخزين المؤشرات، فسيتعين عليك التعامل مع تخصيصها/إلغاء تخصيصها.

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

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

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

أما بالنسبة لواجهات الحاويات:اعتمادًا على تصميمك، ربما ستتمكن من جعلها قالبة أيضًا، ومن ثم سيكون لديهم طرق مثل void push(T* new_element).فكر فيما ستعرفه عن الكائن عندما تريد إضافته إلى حاوية (من نوع غير معروف).من أين سيأتي الكائن في المقام الأول؟دالة ترجع void*؟هل تعلم أنه سيكون قابلاً للمقارنة؟على الأقل، إذا تم تعريف جميع فئات الكائنات المخزنة في التعليمات البرمجية الخاصة بك، فيمكنك جعلها جميعًا ترث من سلف مشترك، على سبيل المثال، Storable, ، واستخدام Storable* بدلاً من void*.

الآن، إذا رأيت أن الكائنات ستتم إضافتها دائمًا إلى الحاوية بطريقة مثل void push(Storable* new_element), ، فلن تكون هناك قيمة مضافة حقًا في جعل الحاوية قالبًا.ولكن بعد ذلك ستعرف أنه يجب تخزين المواد القابلة للتخزين.

أولاً، قبل كل شيء، تعد القوالب وتعدد الأشكال مفاهيم متعامدة وتلعب بشكل جيد معًا.بعد ذلك، لماذا تريد بنية بيانات محددة؟ماذا عن STL أو تعزيز هياكل البيانات (على وجه التحديد حاوية المؤشر) لا يعمل بالنسبة لك.

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

باستخدام تعدد الأشكال، يتبقى لك بشكل أساسي فئة أساسية للحاوية، وفئات مشتقة لأنواع البيانات.يمكن أن تحتوي الفئة الأساسية/الفئات المشتقة على العديد من الوظائف الافتراضية التي تحتاجها، في كلا الاتجاهين.

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

لا تنس أنك قد تجعل حياتك أسهل من خلال الكتابة لكل نوع من الأنواع النموذجية - خاصة إذا كنت بحاجة لاحقًا إلى تحويل أحدها إلى فصل دراسي.

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

وفقط لتكرار ما قاله الآخرون، لم أواجه أبدًا مشكلة في مزج تعدد الأشكال والقوالب، وقد قمت ببعض الأشياء المعقدة معهم.

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

class Whatever
{
   void MyPolymorphicMethod(Container<IComparable*> &listOfComparables);
}

يمكن لهذه الطريقة الآن أن تأخذ أي فرع من الحاوية يحتوي على أي فئة تطبق IComparable، لذلك ستكون مرنة للغاية.

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