حل مقبول لغالبية التحذيرات الموقعة/غير الموقعة؟

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

سؤال

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

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

for (int i = 0; i < someVector.size(); ++i) { /* do stuff */ }

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

for (unsigned i = 0; i < someVector.size(); ++i) { /*do stuff*/ }

يعمل هذا عادةً ولكنه قد ينقطع بصمت إذا كانت الحلقة تحتوي على أي رمز مثل 'if (i-1 >= 0) ...'، وما إلى ذلك.

for (int i = 0; i < static_cast<int>(someVector.size()); ++i) { /*do stuff*/ }

ليس لهذا التغيير أي آثار جانبية، ولكنه يجعل الحلقة أقل قابلية للقراءة.(وهو أكثر كتابة.)

لذلك خطرت لي الفكرة التالية:

template <typename T> struct vector : public std::vector<T>
{
    typedef std::vector<T> base;

    int size() const     { return base::size(); }
    int max_size() const { return base::max_size(); }
    int capacity() const { return base::capacity(); }

    vector()                  : base() {}
    vector(int n)             : base(n) {}
    vector(int n, const T& t) : base(n, t) {}
    vector(const base& other) : base(other) {}
};

template <typename Key, typename Data> struct map : public std::map<Key, Data>
{
    typedef std::map<Key, Data> base;
    typedef typename base::key_compare key_compare;

    int size() const     { return base::size(); }
    int max_size() const { return base::max_size(); }

    int erase(const Key& k) { return base::erase(k); }
    int count(const Key& k) { return base::count(k); }

    map() : base() {}
    map(const key_compare& comp) : base(comp) {}
    template <class InputIterator> map(InputIterator f, InputIterator l) : base(f, l) {}
    template <class InputIterator> map(InputIterator f, InputIterator l, const key_compare& comp) : base(f, l, comp) {}
    map(const base& other) : base(other) {}
};

// TODO: similar code for other container types

ما تراه هو في الأساس فئات STL مع الأساليب التي تُرجع حجم_النوع الذي تم تجاوزه لإرجاع "int" فقط.هناك حاجة إلى المنشئين لأنها غير موروثة.

ما رأيك في هذا كمطور، إذا كنت ترى حلاً كهذا في قاعدة تعليمات برمجية موجودة؟

هل تعتقد "يا إلهي، إنهم يعيدون تعريف STL، يا له من WTF ضخم!"، أو هل تعتقد أن هذا حل بسيط ولطيف لمنع الأخطاء وزيادة إمكانية القراءة.أو ربما تفضل أن ترى أننا قضينا (نصف) يومًا أو نحو ذلك في تغيير كل هذه الحلقات لاستخدام std::vector<>::iterator؟

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

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

المحلول

لقد أنشأت هذا المجتمع ويكي...يرجى تحريره.أنا لا أتفق مع النصيحة ضد "int" بعد الآن.أرى الآن أنه ليس سيئا.

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

إلى الأمام

for(std::vector<int>::size_type i = 0; i < someVector.size(); i++) {
    /* ... */
}

الى الوراء

يمكنك القيام بذلك، وهو سلوك محدد تمامًا:

for(std::vector<int>::size_type i = someVector.size() - 1; 
    i != (std::vector<int>::size_type) -1; i--) {
    /* ... */
}

قريبًا، مع ظهور c++1x (الإصدار التالي من C++) بشكل جيد، يمكنك القيام بذلك على النحو التالي:

for(auto i = someVector.size() - 1; i != (decltype(i)) -1; i--) {
    /* ... */
}

سيؤدي التناقص إلى أقل من 0 إلى التفاف i، لأنه غير موقع.

لكن عدم التوقيع سيؤدي إلى ظهور الأخطاء

لا ينبغي أن يكون ذلك أبدًا حجة لجعل الأمر خاطئًا (باستخدام 'int').

لماذا لا تستخدم std::size_t أعلاه؟

يحدد معيار C++ في 23.1 p5 Container Requirements, ، الذي - التي T::size_type ، ل T كونها بعض Container, ، أن هذا النوع هو بعض أنواع التنفيذ المحددة غير الموقعة.الآن، باستخدام std::size_t ل i أعلاه سوف يسمح للبق بالتسلل بصمت.لو T::size_type أقل أو أكبر من std::size_t, ، ثم سوف يفيض i, ، أو حتى لا تصل إلى (std::size_t)-1 لو someVector.size() == 0.وبالمثل، فإن حالة الحلقة قد تم كسرها بالكامل.

نصائح أخرى

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

هنا، أود فقط استخدام size_t كمتغير الحلقة.انها بسيطة وقابلة للقراءة.الملصق الذي علق على ذلك باستخدام int يعرضك الفهرس على أنه n00b صحيح.ومع ذلك، فإن استخدام مكرر للتكرار فوق ناقل يعرضك كشخص n00b أكثر خبرة - وهو شخص لا يدرك أن عامل التشغيل المنخفض للمتجه هو الوقت الثابت.(vector<T>::size_type دقيق، ولكن IMO مطول بلا داع).

على الرغم من أنني لا أعتقد أن "استخدام التكرارات، وإلا فإنك ستبدو n00b" هو حل جيد للمشكلة، إلا أن الاشتقاق من std::vector يبدو أسوأ بكثير من ذلك.

أولاً، يتوقع المطورون أن يكون المتجه std:.vector، وأن تكون الخريطة std::map.ثانيًا، لا يتناسب الحل الخاص بك مع الحاويات الأخرى أو الفئات/المكتبات الأخرى التي تتفاعل مع الحاويات.

نعم، التكرارات قبيحة، وحلقات التكرار ليست قابلة للقراءة بشكل جيد، وكتابات الكتابة تغطي الفوضى فقط.لكنهم على الأقل يقومون بالتوسع، وهم الحل الأساسي.

بلدي الحل؟stl لكل ماكرو.هذا لا يخلو من المشاكل (في الأساس، إنه ماكرو، مقزز)، لكنه يعبر المعنى.إنها ليست متقدمة مثل على سبيل المثال. هذا, ، لكنه يقوم بهذه المهمة.

بالتأكيد استخدم المكرر.ستتمكن قريبًا من استخدام النوع "تلقائي" لتحسين إمكانية القراءة (أحد اهتماماتك) مثل هذا:

for (auto i = someVector.begin();
     i != someVector.end();
     ++i)

تخطي الفهرس

الطريقة الأسهل هي تجنب المشكلة باستخدام التكرارات، أو حلقات for المستندة إلى النطاق، أو الخوارزميات:

for (auto it = begin(v); it != end(v); ++it) { ... }
for (const auto &x : v) { ... }
std::for_each(v.begin(), v.end(), ...);

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

استخدم نوعًا مناسبًا غير موقع

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

for (std::vector<T>::size_type i = 0; i < v.size(); ++i) { ... }

تستطيع ايضا استخذام std::size_t (من <cstddef>).وهناك من يشير (بشكل صحيح) إلى ذلك std::size_t قد لا يكون من نفس النوع std::vector<T>::size_type (على الرغم من أنه عادة ما يكون كذلك).ومع ذلك، يمكنك التأكد من أن الحاوية size_type سوف يصلح في std::size_t.لذلك كل شيء على ما يرام، إلا إذا كنت تستخدم أنماط معينة للحلقات العكسية.النمط المفضل لدي للحلقة العكسية هو:

for (std::size_t i = v.size(); i-- > 0; ) { ... }

مع هذا النمط يمكنك استخدامه بأمان std::size_t, ، حتى لو كان من النوع الأكبر منه std::vector<T>::size_type.يتطلب نمط الحلقات العكسية الموضح في بعض الإجابات الأخرى إرسال -1 إلى النوع الصحيح تمامًا وبالتالي لا يمكن استخدام النوع الأسهل في الكتابة std::size_t.

استخدم نوعًا موقعًا (بعناية!)

إذا كنت تريد حقًا استخدام نوع موقّع (أو إذا كان لديك دليل الأسلوب يتطلب عمليا واحدا)، يحب int, ، ثم يمكنك استخدام قالب الوظيفة الصغير هذا الذي يتحقق من الافتراض الأساسي في تصميمات تصحيح الأخطاء ويجعل التحويل واضحًا حتى لا تحصل على رسالة تحذير المحول البرمجي:

#include <cassert>
#include <cstddef>
#include <limits>

template <typename ContainerType>
constexpr int size_as_int(const ContainerType &c) {
    const auto size = c.size();  // if no auto, use `typename ContainerType::size_type`
    assert(size <= static_cast<std::size_t>(std::numeric_limits<int>::max()));
    return static_cast<int>(size);
}

الآن يمكنك الكتابة:

for (int i = 0; i < size_as_int(v); ++i) { ... }

أو عكس الحلقات بالطريقة التقليدية:

for (int i = size_as_int(v) - 1; i >= 0; --i) { ... }

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

أنت تبالغ في التفكير في المشكلة.

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

vector.size() يعود أ size_t var، لذلك فقط قم بالتغيير int ل size_t وينبغي أن يكون على ما يرام.

إجابة ريتشارد هي الأصح، إلا أن الأمر يتطلب الكثير من العمل لحلقة بسيطة.

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