Vizualizări, evaluare leneșă și compoziție
Vizualizări Master C++20 — adaptoare ușoare, leneșe, care compun cu operatorul de conducte. Învățați să filtrați, să transformați, să preluați, să plasați, să divizați, să uniți, să aflați cum să construiți conducte puternice de date fără alocări intermediare.
Ce sunt vizualizările?
O vizualizare este o gamă ușoară care este:
1. Neposedare — nu deține elementele la care face referire
2. O(1) copiați și mutați — vizualizările sunt ieftine de transmis
3. Evaluate leneș — elementele sunt calculate la cerere în timpul iterației, nu în avans
Vizualizările sunt elementele de bază ale conductelor de gamă. În loc să creeze containere intermediare la fiecare pas, vizualizările compun într-o singură trecere peste date:
// 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::).
Filtrați și transformați — Vederile fundamentale
views::filterpăstrează elementele care se potrivesc cu un predicat.views::transformaplică o funcție fiecărui element. Împreună, ele înlocuiesc majoritatea buclelor explicite.
#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>();
}luați, plasați și tăiați vizualizările
views::take(n)produce primele n elemente.views::drop(n)omite primele n elemente. Împreună, permit tăierea eficientă fără copiere.
#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';
}Vizualizări împărțite, unite și procesare șiruri
views::splitdivides a range by a delimiter.views::joinflattens nested ranges. These are powerful for string and data processing.
#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) { ... }
}views::iota — Interval de generare
views::iotagenerează o secvență de valori crescătoare. Poate fi mărginit (iota(0, 10)pentru 0..9) sau nemărginit (iota(0)pentru 0, 1, 2, ...). Combinat cutake, înlocuiește multe bucle de numărare.
#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
}Construirea de conducte complete de date
Adevărata putere a vizualizărilor apare atunci când compuneți mai multe adaptoare într-o singură conductă. Întreaga conductă evaluează leneș într-o singură trecere.
#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';
}
}Vizualizările sunt neproprietate - fac referire la intervalul de bază. Dacă domeniul de sursă este distrus, vizualizarea devine atârnând:
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!Asigurați-vă întotdeauna că datele sursă supraviețuiesc tuturor vizualizărilor care le fac referire. Stocați datele într-o variabilă cu o durată de viață suficientă înainte de a crea vizualizări asupra acestora.
Vizualizările sunt leneșe — nu se lucrează până la iterare. Aceasta înseamnă:
1. Construirea unei conducte este în esență gratuită (stochează doar indicatorii de funcție și parametrii)
2. Puteți crea vizualizări pe domenii foarte mari sau chiar infinite
3. Fiecare element curge prin întreaga conductă înainte ca următorul element să fie procesat
4. Vizualizările în scurtcircuit precumtake(n)opresc întreaga conductă după n elemente
Pentru a materializa rezultatele vizualizării într-un container, repetați și colectați:
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>();Preferați să păstrați datele ca vizualizări cât mai mult timp posibil - se materializează numai atunci când aveți nevoie de acces aleatoriu, persistență sau pentru a le transmite către API-uri care necesită containere.
- Vizualizările sunt adaptoare de gamă care nu sunt proprietare, O(1) copiabile, evaluate leneș
views::filterșiviews::transformsunt blocurile fundamentale pentru conductele de dateviews::take(n)șiviews::drop(n)oferă tăiere fără copiereviews::iotagenerează secvențe - mărginite sau nemărginite - înlocuind buclele de numărare- Operatorul de conducte
|compune vederi în conducte lizibile de la stânga la dreapta cu zero alocări intermediare - Vizualizările nu trebuie să depășească datele lor sursă - asigurați-vă întotdeauna că intervalul de bază are o durată de viață suficientă
Test — Verifică-ți cunoștințele
(20 XP)1. Când își calculează elementele o vizualizare C++20?
2. Care este rezultatul`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?