ما هي الطريقة الأكثر أمانًا لتخزين كائنات الفئات المستمدة من واجهة مشتركة في حاوية مشتركة؟
-
21-09-2019 - |
سؤال
أرغب في إدارة مجموعة من كائنات الفئات المستمدة من فئة واجهة مشتركة في حاوية مشتركة.
لتوضيح المشكلة ، دعنا نقول أنني أبني لعبة تحتوي على ممثلين مختلفين. دعنا نسمي الواجهة IActor
واشتقاق Enemy
و Civilian
منه.
الآن ، تتمثل الفكرة في أن تتمكن حلقة اللعبة الرئيسية من القيام بذلك:
// somewhere during init
std::vector<IActor> ActorList;
Enemy EvilGuy;
Civilian CoolGuy;
ActorList.push_back(EvilGuy);
ActorList.push_back(CoolGuy);
و
// main loop
while(!done) {
BOOST_FOREACH(IActor CurrentActor, ActorList) {
CurrentActor.Update();
CurrentActor.Draw();
}
}
... او هناك شيء ما على طول هذه الخطوط. من الواضح أن هذا المثال لن ينجح ، لكن هذا هو السبب في أنني أسأل هنا.
أود أن أعرف: ما هي أفضل طريقة وأكثرها أمانًا وأعلى مستوى لإدارة تلك الكائنات في حاوية شائعة غير متجانسة؟ أعلم عن مجموعة متنوعة من الأساليب (Boost :: any ، void*، فئة Handler مع Boost :: shared_ptr ، boost.pointer container ، dynamic_cast) ولكن لا يمكنني تحديد أي وسيلة للذهاب إلى هنا.
كما أود أن أؤكد أنني أريد الابتعاد إلى أقصى حد ممكن من إدارة الذاكرة اليدوية أو المؤشرات المتداخلة.
مساعدة كبيرة في تقدير :).
المحلول
كما خمنت أنك بحاجة إلى تخزين الكائنات كمؤشرات.
أفضل استخدام حاويات مؤشر Boost (بدلاً من حاوية عادية من المؤشرات الذكية).
والسبب في ذلك هو الوصول إلى حاوية Boost PTR للكائنات كما لو كانت كائنات (المراجع العائدة) بدلاً من المؤشرات. هذا يجعل من الأسهل استخدام الفوذات والخوارزميات القياسية على الحاويات.
عيب المؤشرات الذكية هو أنك تشارك الملكية.
هذا ليس ما تريده حقًا. تريد أن تكون الملكية في مكان واحد (في هذه الحالة الحاوية).
boost::ptr_vector<IActor> ActorList;
ActorList.push_back(new Enemy());
ActorList.push_back(new Civilian());
و
std::for_each(ActorList.begin(),
ActorList.end(),
std::mem_fun_ref(&IActor::updateDraw));
نصائح أخرى
لحل المشكلة التي ذكرتها ، على الرغم من أنك تسير في الاتجاه الصحيح ، لكنك تفعل ذلك بطريقة خاطئة. هذا ما ستحتاج إلى فعله
- حدد فئة أساسية (التي تفعله بالفعل) مع وظائف افتراضية يتم تجاوزها بواسطة الفئات المشتقة
Enemy
وCivilian
في حالتك. - تحتاج إلى اختيار حاوية مناسبة مع تخزين الكائن الخاص بك. لقد أخذت ملف
std::vector<IActor>
وهو ليس خيارًا جيدًا بسبب- أولاً عندما تقوم بإضافة كائنات إلى المتجه ، فإنه يؤدي إلى تقطيع الكائنات. هذا يعني أن فقط
IActor
جزء منEnemy
أوCivilian
يتم تخزينه بدلاً من الكائن بأكمله. - ثانياً ، تحتاج إلى استدعاء الوظائف اعتمادًا على نوع الكائن (
virtual functions
) ، والتي لا يمكن أن تحدث إلا إذا كنت تستخدم المؤشرات.
- أولاً عندما تقوم بإضافة كائنات إلى المتجه ، فإنه يؤدي إلى تقطيع الكائنات. هذا يعني أن فقط
يشير كلا السبب أعلاه إلى حقيقة أنك تحتاج إلى استخدام حاوية يمكن أن تحتوي على مؤشرات ، شيء مثل std::vector<IActor*>
. لكن الخيار الأفضل هو الاستخدام container of smart pointers
مما سيوفر لك من صداع إدارة الذاكرة. يمكنك استخدام أي من المؤشرات الذكية اعتمادًا على حاجتك (ولكن ليس auto_ptr
)
هذا ما سيبدوه رمزك
// somewhere during init
std::vector<some_smart_ptr<IActor> > ActorList;
ActorList.push_back(some_smart_ptr(new Enemy()));
ActorList.push_back(some_smart_ptr(new Civilian()));
و
// main loop
while(!done)
{
BOOST_FOREACH(some_smart_ptr<IActor> CurrentActor, ActorList)
{
CurrentActor->Update();
CurrentActor->Draw();
}
}
الذي يشبه إلى حد كبير رمزك الأصلي باستثناء جزء المؤشرات الذكية
رد فعلي الفوري هو أنه يجب عليك تخزين المؤشرات الذكية في الحاوية ، والتأكد من أن الفئة الأساسية تحدد ما يكفي dynamic_cast
العودة إلى الفئة المشتقة.
إذا كنت تريد أن تملك الحاوية عناصرها حصريًا ، فاستخدم حاوية مؤشر Boost: فهي مصممة لهذه المهمة. خلاف ذلك ، استخدم حاوية من shared_ptr<IActor>
(وبالطبع استخدمها بشكل صحيح ، مما يعني أن كل من يحتاج إلى مشاركة استخدامات الملكية shared_ptr
).
في كلتا الحالتين ، تأكد من أن المدمر IActor
افتراضي.
void*
يتطلب منك القيام بإدارة الذاكرة اليدوية ، لذلك هذا خارج. Boost.Ine يكون مبالغة عندما تكون الأنواع مرتبطة بالميراث - تعدد الأشكال القياسي يقوم بالمهمة.
سواء كنت بحاجة dynamic_cast
أم لا مشكلة متعامدة - إذا كان مستخدمو الحاوية يحتاجون فقط إلى واجهة IACTOR ، وأنت إما (أ) تجعل جميع وظائف الواجهة الظاهرية ، أو (ب) استخدام تعبير الواجهة غير الذروة ، فأنت لا ترتديها "T بحاجة إلى Dynamic_cast. إذا كان مستخدمو الحاوية يعلمون أن بعض كائنات IACTOR هم مدنيون "حقًا" ، ويريدون الاستفادة من الأشياء الموجودة في الواجهة المدنية ولكن ليس iActor ، فستحتاج إلى قوالب (أو إعادة تصميم).