هل تعتبر تهيئة Iterator داخل الحلقة أسلوبًا سيئًا ولماذا؟

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

سؤال

عادة ستجد رمز STL مثل هذا:

for (SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(); Iter != m_SomeMemberContainerVar.end(); ++Iter)
{
}

لكن لدينا في الواقع التوصية بكتابتها على النحو التالي:

SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin();
SomeClass::SomeContainer::iterator IterEnd = m_SomeMemberContainerVar.end();
for (; Iter != IterEnd; ++Iter)
{
}

إذا كنت قلقًا بشأن تحديد النطاق، فأضف الأقواس المرفقة:

{
    SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin();
    SomeClass::SomeContainer::iterator IterEnd = m_SomeMemberContainerVar.end();
    for (; Iter != IterEnd; ++Iter)
    {
    }
}

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

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

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

ما هو رأيك في هذا؟هل هناك أي مميزات للأسلوب الأول بخلاف إمكانية الحد من نطاق Iter؟

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

المحلول

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

for (SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(),
    IterEnd = m_SomeMemberContainerVar.end();
    Iter != IterEnd;
    ++Iter)
{
}

تحديث:تم إصلاح الكود وفقًا لنصيحة بيرسيبال.

نصائح أخرى

البديل الآخر هو استخدام ماكرو foreach، على سبيل المثال تعزيز foreach:

BOOST_FOREACH( ContainedType item, m_SomeMemberContainerVar )
{
   mangle( item );
}

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

هناك أيضًا ملاحظة على الصفحة المرتبطة حول إعادة تعريف BOOST_FOREACH كـ foreach لتجنب الأحرف الكبيرة المزعجة.

يكون النموذج الأول (داخل حلقة for) أفضل إذا لم تكن هناك حاجة إلى المكرر بعد حلقة for.إنه يحد من نطاقه في الحلقة.

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

typedef SomeClass::SomeContainer::iterator MyIter;

for (MyIter Iter = m_SomeMemberContainerVar.begin(); Iter != m_SomeMemberContainerVar.end(); ++Iter)
{
}

أوصي بأسماء أقصر ؛-)

بعد النظر إلى هذا في g++ في تحسين -O2 (فقط لأكون محددًا)

لا يوجد فرق في الكود الذي تم إنشاؤه لـ std::vector وstd::list وstd::map (والأصدقاء).هناك حمل صغير مع std::deque.

لذا بشكل عام، من وجهة نظر الأداء، لا يحدث فرقًا كبيرًا.

لا، إنها فكرة سيئة أن نتمسك بها iter.end() قبل أن تبدأ الحلقة.إذا غيرت حلقتك الحاوية، فقد يتم إبطال مكرر النهاية.أيضا، end() الطريقة مضمونة لتكون O(1).

التحسين المبكر هو أصل كل الشرور.

أيضًا، قد يكون المترجم أكثر ذكاءً مما تعتقد.

ليس لدي رأي قوي بشكل خاص بطريقة أو بأخرى، على الرغم من أن عمر المكرر قد يدفعني نحو الإصدار المخصص.

ومع ذلك، قد تكون سهولة القراءة مشكلة؛يمكن المساعدة في ذلك باستخدام typedef بحيث يكون نوع التكرار أكثر قابلية للإدارة:

typedef SomeClass::SomeContainer::iterator sc_iter_t;

for (sc_iter_t Iter = m_SomeMemberContainerVar.begin(); Iter != m_SomeMemberContainerVar.end(); ++Iter)
{
}

ليس تحسنا كبيرا، ولكن قليلا.

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

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

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

doStuff(coll.begin(), coll.end())

و لدي..

template<typename InIt>
void doStuff(InIt first, InIt last)
{
   for (InIt curr = first; curr!= last; ++curr)
   {
       // Do stuff
   }
 }

أشياء تحبها:

  • لا داعي أبدًا لذكر نوع المكرر القبيح (أو التفكير فيما إذا كان ثابتًا أم لا)
  • إذا كان هناك ربح من عدم استدعاء end() في كل تكرار، فأنا أحصل عليه

أشياء لا تحبها:

  • يفكك الكود
  • الحمل الزائد لاستدعاء وظيفة إضافية.

ولكن في يوم من الأيام، سيكون لدينا لامدا!

لا أعتقد أن هذا أسلوب سيء على الإطلاق.ما عليك سوى استخدام typedefs لتجنب الإسهاب في STL والسطور الطويلة.

typedef set<Apple> AppleSet;
typedef AppleSet::iterator  AppleIter;
AppleSet  apples;

for (AppleIter it = apples.begin (); it != apples.end (); ++it)
{
   ...
}

البرمجة المتقشف هي إحدى الطرق للتخفيف من مخاوف أسلوبك.

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

وأنا أتفق مع فيروتشيو.قد يفضل البعض النمط الأول لسحب استدعاء end() خارج الحلقة.

يمكنني أيضًا أن أضيف أن C++0x سيجعل كلا الإصدارين أكثر نظافة:

for (auto iter = container.begin(); iter != container.end(); ++iter)
{
   ...
}

auto iter = container.begin();
auto endIter = container.end();
for (; iter != endIter; ++iter)
{
   ...
}

كنت عادة أكتب:

SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(),
                                   IterEnd = m_SomeMemberContainerVar.end();

for(...)

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

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