المشاهدات والتقييم البطيء والتكوين
Master C++20 views — lightweight, lazy range adaptors that compose with the pipe operator. Learn filter, transform, take, drop, split, join, iota, and how to build powerful data pipelines without intermediate allocations.
ما هي وجهات النظر؟
العرض هو نطاق خفيف الوزن وهو:
1. عدم الملكية — لا تمتلك العناصر التي تشير إليها
2. O(1) انسخ وانقل — المشاهدات رخيصة الثمن
3. التقييم البطيء — يتم حساب العناصر عند الطلب أثناء التكرار، وليس مقدمًا
المشاهدات هي اللبنات الأساسية لخطوط الأنابيب. بدلاً من إنشاء حاويات متوسطة في كل خطوة، يتم إنشاء طرق العرض في مسار واحد عبر البيانات:
// Traditional: creates 2 intermediate vectors
auto evens = filter(data, is_even); // allocation
auto squared = transform(evens, sq); // allocation
// Views: zero intermediate allocations
auto result = data | views::filter(is_even) | views::transform(sq);
// Nothing computed yet — result is just a view descriptor
for (int x : result) { ... } // computed element-by-elementViews live in std::views:: (a namespace alias for std::ranges::views::).
filter and transform — The Fundamental Views
يحتفظviews::filterبالعناصر المطابقة للمسند. يطبقviews::transformدالة على كل عنصر. معًا، تحل محل غالبية الحلقات الصريحة.
#include <ranges>
#include <vector>
#include <string>
#include <iostream>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Filter even numbers, then square them
auto even_squares = numbers
| std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * x; });
// Nothing has been computed yet — even_squares is a view
for (int x : even_squares) {
std::cout << x << ' '; // 4 16 36 64 100
}
std::cout << '\n';
// Views work with any range — including strings
std::string text = "Hello, World!";
auto uppercase = text
| std::views::filter([](char c) { return std::isalpha(c); })
| std::views::transform([](char c) { return std::toupper(c); });
for (char c : uppercase) {
std::cout << c; // HELLOWORLD
}
std::cout << '\n';
// Collect view results into a vector (C++23: std::ranges::to)
std::vector<int> collected;
for (int x : even_squares) {
collected.push_back(x);
}
// C++23: auto collected = even_squares | std::ranges::to<std::vector>();
}أخذ المشاهدات وإسقاطها وتقطيعها
views::take(n)yields the first n elements.views::drop(n)skips the first n elements. Together they enable efficient slicing without copying.
#include <ranges>
#include <vector>
#include <iostream>
int main() {
std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// take — first N elements
for (int x : data | std::views::take(3)) {
std::cout << x << ' '; // 1 2 3
}
std::cout << '\n';
// drop — skip first N elements
for (int x : data | std::views::drop(7)) {
std::cout << x << ' '; // 8 9 10
}
std::cout << '\n';
// Combine: elements 3-7 (0-indexed: drop 3, take 4)
for (int x : data | std::views::drop(3) | std::views::take(4)) {
std::cout << x << ' '; // 4 5 6 7
}
std::cout << '\n';
// take_while / drop_while — predicate-based
std::vector<int> sorted_data = {1, 3, 5, 7, 2, 4, 6};
auto ascending_prefix = sorted_data | std::views::take_while(
[prev = 0](int x) mutable {
bool ok = x > prev;
prev = x;
return ok;
});
for (int x : ascending_prefix) {
std::cout << x << ' '; // 1 3 5 7
}
std::cout << '\n';
// Pagination example: page 2, page_size = 3
int page = 2, page_size = 3;
auto page_view = data
| std::views::drop((page - 1) * page_size)
| std::views::take(page_size);
for (int x : page_view) {
std::cout << x << ' '; // 4 5 6
}
std::cout << '\n';
}تقسيم، والانضمام، وطرق عرض معالجة السلسلة
views::splitيقسم النطاق بواسطة محدد. يقومviews::joinبتسوية النطاقات المتداخلة. هذه قوية لمعالجة السلسلة والبيانات.
#include <ranges>
#include <string>
#include <string_view>
#include <vector>
#include <iostream>
int main() {
// split — divide a string by delimiter
std::string csv = "Alice,30,Engineer,95000";
for (auto field : csv | std::views::split(',')) {
// Each 'field' is a subrange — convert to string_view
std::string_view sv(field.begin(), field.end());
std::cout << '[' << sv << "] ";
}
std::cout << '\n'; // [Alice] [30] [Engineer] [95000]
// join — flatten nested ranges
std::vector<std::vector<int>> matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
for (int x : matrix | std::views::join) {
std::cout << x << ' '; // 1 2 3 4 5 6 7 8 9
}
std::cout << '\n';
// Combine: split lines, then process each line
std::string multiline = "hello world\nfoo bar\nbaz qux";
int line_num = 1;
for (auto line : multiline | std::views::split('\n')) {
std::string_view line_sv(line.begin(), line.end());
std::cout << line_num++ << ": " << line_sv << '\n';
}
// Enumerate (C++23)
// for (auto [i, val] : data | std::views::enumerate) { ... }
}وجهات النظر::iota - نطاقات التوليد
يقومviews::iotaبإنشاء سلسلة من القيم المتزايدة. يمكن أن يكون محددًا (iota(0, 10)لـ 0..9) أو غير محدود (iota(0)لـ 0، 1، 2، ...). بالاشتراك معtake، فإنه يحل محل العديد من حلقات العد.
#include <ranges>
#include <iostream>
#include <vector>
#include <numeric>
int main() {
// Bounded iota — replaces for(int i = 0; i < 10; ++i)
for (int x : std::views::iota(0, 10)) {
std::cout << x << ' '; // 0 1 2 3 4 5 6 7 8 9
}
std::cout << '\n';
// Unbounded iota + take — first 5 squares
auto first_5_squares = std::views::iota(1)
| std::views::transform([](int x) { return x * x; })
| std::views::take(5);
for (int x : first_5_squares) {
std::cout << x << ' '; // 1 4 9 16 25
}
std::cout << '\n';
// Generate Fibonacci-like sequence with views
// (iota as index generator)
auto indices = std::views::iota(0, 20);
auto even_indices = indices | std::views::filter([](int i) { return i % 2 == 0; });
for (int i : even_indices | std::views::take(5)) {
std::cout << i << ' '; // 0 2 4 6 8
}
std::cout << '\n';
// Use iota to create a vector of sequential values
auto range_vec = std::views::iota(1, 11); // 1 to 10
int sum = 0;
for (int x : range_vec) sum += x;
std::cout << "Sum 1..10: " << sum << '\n'; // 55
}بناء خطوط أنابيب بيانات كاملة
تظهر القوة الحقيقية لطرق العرض عندما تقوم بإنشاء محولات متعددة في مسار واحد. يتم تقييم خط الأنابيب بأكمله بتكاسل في مسار واحد.
#include <ranges>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
struct LogEntry {
std::string level; // "INFO", "WARN", "ERROR"
std::string message;
int timestamp;
};
int main() {
std::vector<LogEntry> logs = {
{"INFO", "Server started", 1000},
{"ERROR", "Connection refused", 1001},
{"INFO", "Request received", 1002},
{"WARN", "Slow query detected", 1003},
{"ERROR", "Timeout expired", 1004},
{"INFO", "Request completed", 1005},
{"ERROR", "Disk full", 1006},
{"WARN", "Memory usage high", 1007},
};
// Pipeline: get recent error messages, formatted
auto recent_errors = logs
| std::views::filter([](const LogEntry& e) {
return e.level == "ERROR";
})
| std::views::drop(1) // skip first error
| std::views::transform([](const LogEntry& e) {
return "[" + std::to_string(e.timestamp) + "] " + e.message;
})
| std::views::take(5); // at most 5 results
std::cout << "Recent errors (after first):\n";
for (const auto& msg : recent_errors) {
std::cout << " " << msg << '\n';
}
// [1004] Timeout expired
// [1006] Disk full
// Reverse view — iterate backward without copying
auto reversed = logs | std::views::reverse | std::views::take(3);
std::cout << "\nLast 3 entries (reversed):\n";
for (const auto& e : reversed) {
std::cout << " [" << e.level << "] " << e.message << '\n';
}
}المشاهدات غير مملوكة — فهي تشير إلى النطاق الأساسي. إذا تم تدمير نطاق المصدر، يصبح العرض متدليًا:
auto make_view() {
std::vector<int> local = {1, 2, 3};
return local | std::views::filter([](int x) { return x > 1; });
// BUG: local is destroyed, view dangles!
}Also beware of views over temporaries created mid-pipeline:
// WRONG: the string is a temporary that gets destroyed
auto words = std::string("hello world")
| std::views::split(' '); // dangling!تأكد دائمًا من أن بيانات المصدر تتجاوز عمر جميع طرق العرض التي تشير إليها. قم بتخزين البيانات في متغير بعمر كافٍ قبل إنشاء طرق عرض عليه.
طرق العرض كسولة — لا يحدث أي عمل حتى التكرار. هذا يعني:
1. يعد إنشاء خط الأنابيب أمرًا مجانيًا بشكل أساسي (يقوم فقط بتخزين المؤشرات والمعلمات الوظيفية)
2. يمكنك إنشاء طرق عرض على نطاقات كبيرة جدًا أو حتى لا نهائية
3. يتدفق كل عنصر عبر خط الأنابيب بأكمله قبل معالجة العنصر التالي
4. تؤدي طرق العرض ذات الدائرة القصيرة مثلtake(n)إلى إيقاف خط الأنابيب بالكامل بعد عناصر n
لتجسيد نتائج العرض في حاوية، قم بالتكرار والتجميع:
std::vector<int> results;
for (int x : some_view) results.push_back(x);
// C++23 adds std::ranges::to for cleaner materialization:
auto results = some_view | std::ranges::to<std::vector>();فضل الاحتفاظ بالبيانات كمشاهدات لأطول فترة ممكنة - ولا تتحقق إلا عندما تحتاج إلى الوصول العشوائي أو الثبات أو التمرير إلى واجهات برمجة التطبيقات التي تتطلب حاويات.
- طرق العرض غير مملوكة، O(1) قابلة للنسخ، ومحولات النطاق التي تم تقييمها بتكاسل
- يعد
views::filterوviews::transformاللبنات الأساسية لخطوط أنابيب البيانات - يوفر
views::take(n)وviews::drop(n)إمكانية التقطيع بدون نسخ - يقوم
views::iotaبإنشاء تسلسلات - محدودة أو غير محدودة - لتحل محل حلقات العد - يقوم مشغل الأنابيب
|بتكوين طرق عرض في خطوط أنابيب يمكن قراءتها من اليسار إلى اليمين مع عدم تخصيص أي تخصيصات وسيطة - يجب ألا تتجاوز مدة العروض بيانات المصدر الخاصة بها - تأكد دائمًا من أن النطاق الأساسي يتمتع بعمر كافٍ
اختبار — اختبر معرفتك
(20 نقطة خبرة)1. متى يقوم عرض C++20 بحساب عناصره؟
2. ما هي نتيجة`std::views::iota(1) | std::views::take(3)`؟
3. Why is it dangerous to create a view over a local variable and return it from a function?