C++ / دليل حلول تحريري

إصلاح إبطال المكررات بعد تغيير الحاوية

حدّد عملية التعديل وقاعدة الحاوية بدقة، ثم اختر اجتيازًا يبقى صالحًا عند نقل العناصر أو حذفها.

وقت القراءة التقريبي: 3 دقيقةتاريخ مراجعة المحتوى

قاعدة معرفة للقراءة فقط · بلا تعليقات عامة

تعرّف على الأعراض

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

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

افهم الأسباب المحتملة

  • تبطل إعادة تخصيص vector كل المراجع والمؤشرات والمكررات إلى تخزينه القديم. وحتى دون إعادة تخصيص تبطل push_back مكرر ما بعد النهاية القديم. وتبطل erase مواضع الوصول عند العنصر المحذوف وبعده بما فيها النهاية القديمة. تختلف قواعد الحاويات؛ فلا تنتقل قاعدة list إلى vector.
  • تحصل حلقة النطاق على حالة الاجتياز قبل التكرار. قد يبطل الإلحاق بالمتجه نفسه هذه الحالة حتى لو كان متغيّر الحلقة نسخة int. يتفادى الفهرس عنوانًا قديمًا فقط إذا بقي الموضع المنطقي ذا معنى؛ وقد يغير حذف عناصر سابقة أو ترتيبها هوية العنصر.

تحديد العملية المبطلة

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

cpp افتراض غير آمن لمرجع محفوظ
#include <vector>
void broken() {
    std::vector<int> values{1};
    int& saved = values[0];
    values.push_back(2);
    saved = 9; // invalid if growth reallocated storage
}

لا يلزم التنفيذ. قد تبطل push_back المرجع saved فلا تملك الكتابة الأخيرة حجة عمر صالحة عمومًا.

المتابعة من الموضع الذي تعيده erase

عند الحذف أثناء الاجتياز استخدم المكرر التالي الصالح الذي تعيده erase بدل زيادة المكرر المحذوف. زِد المكرر فقط عند عدم الحذف. يزيل البرنامج الأعداد الزوجية مع إبقاء كل وصول ضمن النطاق الحالي الصالح. قد يعبّر نمط erase-remove أو std::erase_if من C++20 بصورة أقصر، لكن الحلقة توضّح انتقال المكرر.

cpp اجتياز حذف صالح
#include <iostream>
#include <vector>
int main() {
    std::vector<int> values{1, 2, 3, 4, 5};
    for (auto position = values.begin(); position != values.end();) {
        if (*position % 2 == 0) { position = values.erase(position); }
        else { ++position; }
    }
    for (int value : values) { std::cout << value << " "; }
    std::cout << "size=" << values.size() << "\n";
}
الناتج المتوقع
1 3 5 size=3

يُحذف اثنان وأربعة. يحدّد المكرر المعاد العنصر التالي الحالي، وتقارن الحلقة مع النهاية الحالية.

فصل الاجتياز عن النمو البنيوي

إذا لزم إلحاق سجلات جديدة فاجمعها في متجه منفصل وألحقها بعد اكتمال القراءة. أو استخدم فهرسة بعدد أصلي ثابت مع إعادة الحصول على العنصر بعد كل نمو دون حفظ مرجعه عبره. ولثبات الهوية عبر الحذف والترتيب احفظ معرّفات التطبيق وابحث عنها. لا تختَر حاوية أخرى إلا بعد فحص ثباتها ومحليتها وتعقيدها.

تحقق من نجاح الإصلاح

شغّل البرنامج المصحح في C++20 وقارن سطره الدقيق: 1 3 5 size=3. أضف حالات فارغة وحذف الجميع وعدم الحذف وحذف المتجاورين. تفحص هذه الانتهاء ومسار الموضع المعاد بدل الاعتماد على حالة سعة واحدة.

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

امنع تكرار المشكلة

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

المراجع الرسمية