C++ / المتقدم / الدرس 14
النطاقات والعروض والاستعارة
ركّب عمليات نطاقات C++20 مع توضيح التقييم الكسول والتعديل وعمر التخزين الأساسي.
في هذه الصفحة
أهداف الدرس
- استخدام خوارزميات النطاقات وعروض filter وtransform الكسولة.
- تمييز الحاوية المالكة من span أو عرض غير مالك.
- تجنب إعادة خطوط معالجة تستعير تخزينًا مدمرًا.
العمل على النطاق كوحدة
يوفر النطاق بداية ونهاية ممثلة بحارس. تقبل خوارزميات النطاقات النطاق مباشرة وتستخدم المفاهيم لبيان شروط الاجتياز والاستدعاء. ليس كل حارس من نوع المكرر، ولا كل نطاق معلوم الحجم أو قابلًا لتكرار المرور. العرض تجريد نطاق خفيف له شروط دلالية وتعقيدية محددة، ولا تعني الكلمة أنه لا يملك بيانات مطلقًا. تملك بعض العروض نطاقًا وتستعير أخرى. افحص المحوّل والمصدر المحددين بدل استنتاج العمر من وجود رمز الأنبوب.
خط المعالجة يصف العمل قبل تنفيذه
تؤجل عروض filter وtransform عادة معالجة العناصر إلى الاجتياز. في المثال الأول يعيش المتجه lvalue أطول من خط المعالجة، ويختار الشرط الأعداد الزوجية ويُربّع التحويل كل قيمة مختارة. لا ينشئ بناء الخط متجه مربعات. قد يعيد الاجتياز العمل، فأبقِ الشروط والتحويلات بلا آثار خفية ولا تعتمد على عدد استدعاءات محدد. القيم صغيرة والضرب آمن. خزّن النتائج صراحة إذا احتاج المستهلكون لاحقًا إلى لقطة مستقلة.
#include <iostream>
#include <ranges>
#include <vector>
int main() {
const std::vector<int> values{1, 2, 3, 4};
auto squares = values
| std::views::filter([](int value) { return value % 2 == 0; })
| std::views::transform([](int value) { return value * value; });
int total{};
for (int value : squares) { std::cout << value << " "; total += value; }
std::cout << "total=" << total << "\n";
}
4 16 total=20
يمر اثنان وأربعة من المرشح ويصبحان أربعة وستة عشر. مجموعهما عشرون دون ادعاء عدد استدعاءات الشرط.
span يستعير عناصر متجاورة
std::span<T> عرض غير مالك لعناصر متجاورة مع عددها. يتيح تمرير span<const int> للدالة قراءة مصفوفات أو متجهات دون نسخ العناصر أو كشف حاوية محددة. لا يبقي span مصدره حيًا ولا يمنع إعادة تخصيص المتجه. ويظل operator[] فيه مشروطًا بفهرس صالح في C++20؛ فوجود حجم لا يعني فحصًا آليًا. يستخدم البرنامج الثاني التكرار النطاقي فيما يبقى المتجه المالك حيًا ودون تعديل. يستطيع span ذو الامتداد الديناميكي تمثيل إدخال فارغ طبيعيًا.
#include <iostream>
#include <span>
#include <vector>
int sum(std::span<const int> values) {
int total{};
for (int value : values) { total += value; }
return total;
}
int main() {
const std::vector<int> values{2, 3, 5};
const std::span<const int> borrowed{values};
std::cout << borrowed.size() << " " << sum(borrowed) << "\n";
}
3 10
يرى span ثلاثة عناصر مجموعها عشرة. لا يغادر عمر المتجه ولا يكتسب ملكية.
الكسل يبقي اعتمادات العمر
تترك إعادة عرض filter فوق متجه محلي الخط المعاد يستعير تخزينًا مدمرًا. أعد متجهًا مملوكًا أو اقبل نطاقًا يملكه المستدعي بعمر موثق أو صمّم نطاقًا مالكًا صراحة. تعيد بعض خوارزميات النطاقات std::ranges::dangling مع نطاقات مؤقتة غير مناسبة، فتمنع تسربات مكررات معينة، لكنها لا تجعل كل عرض مركب آمنًا. لا تعدّل المصدر بنيويًا أثناء الاجتياز. قد تخزّن عروض filter حالة الاجتياز، فتحتاج تغييرات العناصر المؤثرة في الشرط أثناء إعادة الاستخدام إلى مراجعة شروط المحوّل الدقيقة.
فصل ميزات نطاقات C++20 عما بعدها
تشمل C++20 خوارزميات النطاقات ومحوّلي filter وtransform وspan المستخدمة هنا. تنتمي مكوّنات مثل std::ranges::to إلى C++23، فلا تستخدمها ضمنيًا في حل C++20. اجمع خط C++20 في متجه مالك بحلقة مناسبة أو إنشاء يدعمه زوج المكرر والحارس بعد فحص شروطهما. يبقى دعم اللغة منفصلًا عن دعم المكتبة. فضّل حلقة واضحة حين تخفي سلسلة محوّلات معقدة الملكية أو معالجة الخطأ أو كلفة إعادة الاجتياز. يجب أن يسهّل التجريد فحص العقد.
الأخطاء الشائعة وإصلاحها
عرض معاد يقرأ تخزينًا ميتًا.
- لماذا يحدث؟
- يستعير الخط حاوية محلية.
- كيف تصلحه؟
- أعد نتائج مملوكة أو اشترط مصدرًا حيًا يملكه المستدعي.
- كيف تتجنب تكراره؟
- تتبّع الملكية عبر كل محوّل.
بناء C++20 يرفض ranges::to.
- لماذا يحدث؟
- تستخدم الشيفرة ميزة من C++23.
- كيف تصلحه؟
- اجمع بحلقة C++20 أو غيّر المعيار الموثق.
- كيف تتجنب تكراره؟
- افحص إصدار الميزة وجداول دعم المكتبة.
span محفوظ يتعطل بعد الإلحاق.
- لماذا يحدث؟
- أعاد المتجه تخصيص العناصر المستعارة.
- كيف تصلحه؟
- أعد إنشاء span بعد التعديل وافحص امتداده.
- كيف تتجنب تكراره؟
- وثّق العمليات البنيوية المبطلة للعروض.
اختبر فهمك
1. غيّر شرط الخط لاختيار القيم الفردية. توقّع القيم المطبوعة والمجموع، واذكر هل ينفذ إنشاء الخط التربيع فورًا.
اعرض الحل المشروح
يمر واحد وثلاثة فيكون السطر "1 9 total=10" ثم سطر جديد. يصف إنشاء الخط تحويلات كسولة، ويحصل الاجتياز على القيم المحوّلة. أبقِ المتجه الأصلي حيًا ودون تغيير. لا تستخدم آثارًا جانبية لعد استدعاءات الشرط، فليست جزءًا من العقد المقصود.
2. صمّم دالة ترشّح متجهًا مؤقتًا وتعيد نتائج آمنة في C++20. حدّد نوع الإرجاع والملكية بعد العودة.
اعرض الحل المشروح
اقبل المتجه بالقيمة واجتز القيم المختارة إلى std::vector<int> محلي باسم result ثم أعده بالقيمة. يملك المستدعي العناصر المعادة مستقلًا بعد إتلاف معامل الإدخال. أما عرض يستعير المعامل فيكون غير صالح. التخزين الإضافي صريح ويُقاس بعد إثبات الصحة.
أهم ما تعلمته
- يبقى العمل الكسول معتمدًا على تخزين حي.
- تحتاج العروض وspan إلى تحليل ملكية صريح.
- استخدم الميزات المتاحة في المعيار المعلن فقط.