هل يقوم pop_back() بالفعل بإبطال *جميع* التكرارات على std::vector؟

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

  •  09-06-2019
  •  | 
  •  

سؤال

std::vector<int> ints;

// ... fill ints with random values

for(std::vector<int>::iterator it = ints.begin(); it != ints.end(); )
{
    if(*it < 10)
    {
        *it = ints.back();
        ints.pop_back();
        continue;
    }
    it++;
}

هذا الرمز لا يعمل لأنه متى pop_back() يسمى، it تم إبطاله.لكنني لا أجد أي مستند يتحدث عن إبطال التكرارات في std::vector::pop_back().

هل لديك بعض الروابط حول ذلك؟

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

المحلول

الدعوة الى pop_back() يزيل العنصر الأخير في المتجه وبالتالي يتم إبطال مكرر هذا العنصر.ال pop_back() المكالمة لا لا إبطال المكررات للعناصر قبل العنصر الأخير، فقط إعادة التخصيص هي التي ستفعل ذلك.من "مرجع مكتبة C++ القياسية" لـ Josuttis:

إدراج العناصر أو إزالتها تبطل المراجع والمؤشرات والمؤشرات التي تشير إلى العنصر التالي.إذا تسبب الإدراج في إعادة التخصيص ، فإنه يبطل جميع المراجع والتكرار والمؤشرات.

نصائح أخرى

إليك إجابتك مباشرة من المعيار المقدس:

23.2.4.2 يفي المتجه بجميع متطلبات الحاوية والحاوية القابلة للعكس (الواردة في جدولين في 23.1) والتسلسل، بما في ذلك معظم متطلبات التسلسل الاختياري (23.1.1).
23.1.1.12 الجدول 68 Expressiona.pop_back () إرجاع الدلالات التشغيليةأ.مسح(--a.end())حاوية ناقلات، قائمة، deque

لاحظ أن a.pop_back يعادل a.erase(--a.end()).النظر في تفاصيل المتجه عند المسح:

23.2.4.3.3 - مسح المكرر (موضع المكرر) - ​​التأثيرات - يبطل كافة التكرارات والمراجع بعد نقطة المسح

لذلك، بمجرد استدعاء pop_back، سيتم إبطال أي تكرارات للعنصر النهائي سابقًا (والذي لم يعد موجودًا الآن).

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

(أستخدم نظام الترقيم كما هو مستخدم في مسودة العمل C++0x، يمكن الحصول عليها هنا

يوضح الجدول 94 في الصفحة 732 أن pop_back (إذا كان موجودًا في حاوية تسلسل) له التأثير التالي:

{ iterator tmp = a.end(); 
--tmp; 
a.erase(tmp); } 

23.1.1، النقطة 12 تنص على ما يلي:

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

كلا الوصول إلى end() كبادئة تطبيقية ليس له مثل هذا التأثير، ومع ذلك، draw():

23.2.6.4 (فيما يتعلق بالنقطة Vector.erase() 4):

تأثيرات:يبطل التكرارات والمراجع عند أو بعد نقطة المسح.

لذلك في الختام:لن يؤدي pop_back() إلا إلى إبطال مكرر العنصر الأخير، وفقًا للمعيار.

فيما يلي اقتباس من وثائق SGI الخاصة بـ STL (http://www.sgi.com/tech/stl/Vector.html):

[5] يتم إبطال مكررات المتجه عند إعادة تخصيص ذاكرته.بالإضافة إلى ذلك، يؤدي إدراج أو حذف عنصر في منتصف المتجه إلى إبطال جميع التكرارات التي تشير إلى العناصر التي تتبع نقطة الإدراج أو الحذف.ويترتب على ذلك أنه يمكنك منع إبطال مكررات المتجه إذا كنت تستخدم Reserve() للتخصيص المسبق لأكبر قدر من الذاكرة سيستخدمه المتجه على الإطلاق، وإذا كانت جميع عمليات الإدراج والحذف في نهاية المتجه.

أعتقد أنه يترتب على ذلك أن pop_back يبطل فقط المكرر الذي يشير إلى العنصر الأخير ومكرر end().نحتاج حقًا إلى رؤية البيانات التي يفشل فيها الكود، بالإضافة إلى الطريقة التي يفشل بها في تحديد ما يحدث.بقدر ما أستطيع أن أقول، يجب أن يعمل الكود - المشكلة المعتادة في مثل هذا الكود هي أن إزالة العنصر و ++ على المكرر يحدث في نفس التكرار، بالطريقة التي يشير إليهاmikhaild.ومع ذلك، في هذا الكود ليس الأمر كذلك:لا يحدث it++ عند استدعاء pop_back.

قد يستمر حدوث شيء سيئ عندما يشير إلى العنصر الأخير، والعنصر الأخير أقل من 10.نحن الآن نقارن بين يبطل - فسخ ذلك وانتهى ().ربما لا يزال يعمل، ولكن لا يمكن تقديم أي ضمانات.

يتم إبطال التكرارات فقط عند إعادة تخصيص مساحة التخزين.جوجل هو صديقك: انظر الحاشية 5.

الكود الخاص بك لا يعمل لأسباب أخرى.

pop_back() يبطل فقط التكرارات التي تشير إلى العنصر الأخير.من مرجع مكتبة C++ القياسية:

إدراج العناصر أو إزالتها تبطل المراجع والمؤشرات والمؤشرات التي تشير إلى العنصر التالي.إذا تسبب الإدراج في إعادة التخصيص ، فإنه يبطل جميع المراجع والتكرار والمؤشرات.

لذا فالجواب على سؤالك، لا لا يبطل الجميع التكرارات.

ومع ذلك، في مثال التعليمات البرمجية الخاص بك، يمكن إبطاله it عندما يشير إلى العنصر الأخير وتكون القيمة أقل من 10.في هذه الحالة، سيقوم Visual Studio debug STL بوضع علامة على المكرِّر على أنه غير صالح، وسيُظهر التحقق الإضافي من أنه لا يساوي end() تأكيدًا.

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

الخطأ هو أنه عندما يشير "it" إلى العنصر الأخير في المتجه وإذا كان هذا العنصر أقل من 10، تتم إزالة هذا العنصر الأخير.والآن يشير "it" إلى ints.end()، وبعد ذلك يقوم "it++" بنقل المؤشر إلى ints.end()+1، لذا الآن "it" يهرب من ints.end()، وحصلت على حلقة لا نهائية لمسح كل ما تبذلونه من ذاكرة :).

"المواصفات الرسمية" هي معيار C++.إذا لم يكن لديك إمكانية الوصول إلى نسخة من C++03، فيمكنك الحصول على أحدث مسودة من C++0x من موقع اللجنة على الويب: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2723.pdf

يحدد قسم "دلالات العمليات" في متطلبات الحاوية أن pop_back() يعادل { iterator i = end();--أنا؛مسح (أنا)؛}.يقول قسم [vector.modifiers] للمسح "التأثيرات:يبطل التكرارات والمراجع عند أو بعد نقطة المسح."

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

سيتم إبطال pop_back() فقط هو - هي لو هو - هي كان يشير إلى العنصر الأخير في المتجه.وبالتالي، ستفشل التعليمات البرمجية الخاصة بك عندما يكون آخر int في المتجه أقل من 10، كما يلي:

*it = ints.back();// اضبطه على القيمة الموجودة به بالفعل
ints.pop_back();// إبطال المكرر
يكمل؛// قم بالتكرار والوصول إلى المكرر غير الصالح

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

for(std::vector<int>::iterator it = ints.begin(); it != ints.end(); )
{
    if(*it < 10)
        it = ints.erase( it );
    else
        ++it;
}

std::remove_if يمكن أن يكون أيضًا حلاً بديلاً.

struct LessThanTen { bool operator()( int n ) { return n < 10; } };

ints.erase( std::remove_if( ints.begin(), ints.end(), LessThanTen() ), ints.end() );

std::remove_if (مثل الخوارزمية الأولى الخاصة بي) مستقرة، لذا قد لا تكون الطريقة الأكثر فعالية للقيام بذلك، ولكنها موجزة.

تحقق من المعلومات هنا (cplusplus.com):

حذف العنصر الأخير

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

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