Форум программистов, компьютерный форум, киберфорум
bytestream
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  

Представления как элементы данных для пользовательских итераторов - Введение

Запись от bytestream размещена 02.08.2025 в 21:55
Показов 3444 Комментарии 0

Нажмите на изображение для увеличения
Название: Представления как элементы данных для пользовательских итераторов.jpg
Просмотров: 372
Размер:	169.6 Кб
ID:	11027
Когда я впервые столкнулся с представлениями (views) в C++20, то сразу понял - это игра по новым правилам. В мире, где производительность по-прежнему стоит во главе угла, а память все дороже, возможность создавать абстракции без накладных расходов выглядит почти как магия. Но это не магия, а реальный инструментарий стандартной библиотеки. Многие продолжают мучиться с реализацией собственных итераторов по старинке, не подозревая, что C++20 приготовил для них мощный подарок. Представления - это не просто новый способ взглянуть на контейнеры, это фундаментально иной подход к проектированию итераторов, который устраняет массу болезненых проблем.

В этой статье я покажу, как использовать представления в качестве элементов данных для создания пользовательских итераторов. Мы разберём реальный пример итератора для обхода многомерных структур данных и увидим, как C++20 позволяет превратить десятки строк кода в элегантное и эффективное решение. Стандартная библиотека сделает за нас всю грязную работу - нам останется только правильно её использовать.

Зачем нужны пользовательские итераторы в современном C++



Даже после двадцати лет эволюции C++ создание собственных итераторов остаётся больной мозолью многих. Я ловлю себя на мысли, что практически в каждом серьезном проекте рано или поздно возникает потребность написать свой итератор. И это не потому, что нам так хочется - просто реальные задачи часто выходят за рамки того, что предлагает стандартная библиотека.

Возьмем простой пример: у вас есть вектор векторов, и нужно обойти все его элементы "насквозь", словно это один плоский массив. Казалось бы, тривиальная задача - но в C++17 и ранее вы вынуждены были либо городить сложную реализацию итератора с отслеживанием внутренних и внешних индексов, либо использовать вложенные циклы, разбросанные по всему коду.

C++
1
2
3
4
5
6
7
// Типичный код без пользовательского итератора
std::vector<std::vector<int>> data = {{1, 2}, {3}, {}, {4, 5, 6}};
for (const auto& outer : data) {
    for (const auto& inner : outer) {
        process(inner); // Обработка каждого элемента
    }
}
Не самый страшный код, но что если нам нужно применить такой подход в десятке мест? А если структура данных сложнее? Или если логика обхода включает условия пропуска элементов?

Ограничения стандартных контейнеров



Стандартные контейнеры дают нам итераторы, которые работают отлично... пока мы не выходим за границы их прямого назначения. Но в реальном мире мы постоянно сталкиваемся с нестандартными случаями:

1. Необходимость итерировать одновременно по нескольким контейнерам.
2. Итерация с пропуском определённых элементов по сложным правилам.
3. Обход структур, которые вообще не являются контейнерами в привычном понимании (например, дерево DOM).
4. Генерация последовательностей "на лету" без их физического хранения.

И каждый раз приходится изобретать велосипед заново. А ведь правильная реализация итератора - это не шутки. Нужно разобраться со всеми категориями итераторов (forward, bidirectional, random_access), с операторами инкремента/декремента, разыменования, сравнения... И всё это с соблюдением правильной семантики.

От императивных алгоритмов к декларативным диапазонам



До C++20 алгоритмы STL требовали передачи пар итераторов и были не очень удобны для композиции. Приходилось писать что-то вроде:

C++
1
2
3
4
5
6
7
std::vector<int> v = {1, 2, 3, 4, 5};
std::vector<int> result;
std::transform(v.begin(), v.end(), std::back_inserter(result), 
               [](int i) { return i * i; });
std::vector<int> filtered;
std::copy_if(result.begin(), result.end(), std::back_inserter(filtered),
             [](int i) { return i > 10; });
Громоздко, не правда ли? И с каждой операцией мы создаём временный контейнер, что совсем не похоже на оптимальный код.
Библиотека диапазонов (ranges) в C++20 кардинально меняет ситуацию, позволяя писать код в более декларативном стиле:

C++
1
2
3
4
5
auto result = v | std::views::transform([](int i) { return i * i; })
               | std::views::filter([](int i) { return i > 10; });
for (int i : result) {
    use(i);
}
Такой код не только короче и понятнее, но и потенциально эффективнее, поскольку представления (views) не создают промежуточных коллекций - они просто описывают, как должна выглядеть итерация.

Избавление от хрупкого метапрограммирования



Для тех, кто пытался использовать SFINAE для создания обобщенных итераторов, подходящих для разных типов данных, C++20 предлагает концепты (concepts) - гораздо более читабельный и понятный способ выражения требований к типам. Вместо сложных enable_if конструкций теперь можно просто писать:

C++
1
2
3
4
template <std::ranges::range R>
class MyCustomIterator {
    // ...
};
И компилятор сразу проверит, соответствует ли тип R концепту range, и выдаст понятную ошибку, если это не так.
Все эти возможности не просто делают код чище - они ткрывают принципиально новый способ создания и использования итераторов. Но самое интересное ещё впереди: как использовать представления в качестве элементов данных внутри собственных итераторов.

Миграция с итераторов STL без потери производительности



Если вы, как и я, годами писали итераторы по стандартам C++98/11/14/17, то переход на C++20 может вызвать нервный тик: "Зачем менять подход, который работал десятилетиями?". И это правильный вопрос! Любая миграция должна давать ощутимые преимущества, иначе игра не стоит свеч. На практике переход на представления из C++20 даёт нам сразу несколько выигрышей:
  • Значительно меньше шаблонного кода;
  • Лучшая поддержка компилятором (более понятные сообщения об ошибках);
  • Совместимость с алгоритмами ranges без дополнительной адаптации;
  • Иногда даже лучшая производительность за счёт оптимизаций времени компиляции.

Когда я впервые переписал свой старый итератор с использованием представлений, размер кода сократился примерно втрое. При этом производительность осталась на том же уровне - в конце концов, представления разрабатывались с учетом того, что C++ это язык, где каждый такт процессора на счету.

Интеграция с метапрограммированием



Еще одна область, где пользовательские итераторы играют критическую роль - это метапрограммирование. Когда вы создаете обобщенные библиотеки, которые должны работать с разными типами данных, вам нередко приходится писать итераторы, адаптирующиеся к разным ситуациям. До C++20 это было настоящим испытанием. Вот пример, который я однажды написал для адаптации итератора к разным типам коллекций:

C++
1
2
3
4
5
6
7
8
template <typename Container, 
         typename = typename std::enable_if<
             has_begin_end<Container>::value &&
             !is_associative_container<Container>::value
         >::type>
class GenericIterator {
// ...много-много кода с SFINAE и static_assert
};
С появлением концептов и ограничений (constraints) тот же самый код становится более читаемым и безопасным:

C++
1
2
3
4
5
template <typename Container> 
requires std::ranges::range<Container> && !is_associative_v<Container>
class GenericIterator {
// ... гораздо более чистый код
};
И эта разница становится критической, когда вы работаете с большими кодовыми базами или сложными обобщенными библиотеками.

Производительность без компромиссов



Многие разработчики опасаются, что использование более высокоуровневых абстракций неизбежно ведет к потере производительности. Однако с представлениями в C++20 мы получаем почти идеальный баланс: высокоуровневый интерфейс и производительность на уровне низкоуровневого кода.
Это достигается за счёт того, что представления - это "ленивые" адаптеры, которые не выполняют вычислений до момента фактического доступа к элементам. Они не создают временных копий данных, а просто описывают, как должен выглядеть процесс итерации.
Я провел несколько бенчмарков, сравнивая традиционный подход с вложенными циклами и подход с использованием представлений для обхода вложенных структур. Разница в производительности была в пределах статистической погрешности, но разница в читаемости и сопровождаемости кода - колоссальная.

Отказ от ручной обработки граничных случаев



Любой, кто писал итератор с нуля, знает насколько муторно обрабатывать всевозможные граничные случаи:
Что делать, если контейнер пуст?
Как корректно обработать достижение конца?
Что возвращать при разыменовании end() итератора?

С представлениями большинство этих проблем решается автоматически, потому что библиотека ranges уже позаботилась об этих случаях. Это уменьшает вероятность ошибок и делает код более надежным.

Однажды я потратил почти день, отлаживая ошибку в своем итераторе, которая проявлялась только когда входной контейнер был пуст. С представлениями такая ошибка была бы просто невозможна.

Как удалить элементы из map без итераторов?
#include &lt;map&gt; #include &lt;string&gt; int main() { std::map &lt;int, int&gt; myMap; } Добавляю в...

Как получить нужные данные из представления , а именно строки представления
Доброго времени суток. на главной форме у меня информация выводится через представление zews......

nullptr для итераторов
Присваивание итератору происходит только при некоторых условиях. Как мне определить итератор...

Реализация итераторов для собственного контейнера
Все вродебы да работает и даже хорошо, но есть 1 но. На cbegin и cend получаю такой error, и не...


Концепция представлений в C++20



Понимание представлений (views) - это ключ к освоению нового подхода в C++20. После 20 лет работы с STL я до сих пор поражаюсь, насколько элегантно это решение. Давайте разберем, что такое представления и почему они меняют правила игры.

Отличия от традиционных контейнеров



Первое, что нужно усвоить о представлениях - они принципиально отличаются от контейнеров. В отличие от std::vector или std::list, представления:
  • Не владеют данными, а лишь "смотрят" на них,
  • Обычно весят всего несколько байт (размером с пару указателей),
  • Не выделяют память для хранения элементов,
  • Могут быть скопированы практически бесплатно.

Если контейнер - это холодильник, где хранятся продукты, то представление - это скорее окно, через которое вы можете их увидеть (возможно, в определенном порядке или только некоторые из них).

C++
1
2
3
4
5
// Контейнер - владеет данными
std::vector<int> nums = {1, 2, 3, 4, 5};
 
// Представление - не владеет данными, просто "смотрит" на них
auto even_nums = nums | std::views::filter([](int n) { return n % 2 == 0; });
Когда мы создаем even_nums, мы не копируем ни одного элемента из nums. Мы просто создаем "окно", через которое будут видны только четные числа.

Ленивые вычисления и производительность



Вторая фундаментальная особенность представлений - ленивые вычисления. Я помню, как раньше мне приходилось писать что-то вроде:

C++
1
2
3
4
5
6
7
8
9
10
11
12
// Старый подход - создаем временные коллекции на каждом шаге
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> squares;
for (int n : numbers) {
    squares.push_back(n * n);
}
std::vector<int> even_squares;
for (int sq : squares) {
    if (sq % 2 == 0) {
        even_squares.push_back(sq);
    }
}
С представлениями эта же задача решается лаконично и эффективно:

C++
1
2
auto result = numbers | std::views::transform([](int n) { return n * n; })
                     | std::views::filter([](int sq) { return sq % 2 == 0; });
Ключевое отличие: в этом коде не происходит никаких вычислений, пока мы фактически не начнем итерироваться по result! Каждый элемент будет обработан только когда (и если) мы до него доберемся. Представьте, что если нам нужен только первый результат - мы сэкономим огромное количество ненужных вычислений.

Композиция операций без промежуточных копий



Сила представлений раскрывается, когда мы начинаем их комбинировать. Оператор | (pipe) позволяет составлять элегантные цепочки трансформаций:

C++
1
2
3
4
auto interesting_data = data 
    | std::views::filter([](auto& item) { return item.is_valid(); })
    | std::views::transform([](auto& item) { return process(item); })
    | std::views::take(10);
Каждое промежуточное представление - это легковесный объект, не содержащий копий данных. Это позволяет писать высокоуровневый код без ущерба для производительности.
Однажды я переписал участок кода, где было 5 последовательных преобразований с временными векторами, на представления - и получил 10-кратный прирост производительности просто за счет устранения ненужных аллокаций и копирований.

Связь с концепциями для типобезопасности



Представления неразрывно связаны с другой мощной фичей C++20 - концепциями (concepts). Библиотека диапазонов определяет концепт std::ranges::view, который формализует требования к типу, чтобы он считался представлением:

C++
1
2
3
4
5
6
template<class T>
concept view =
  range<T> &&
  movable<T> &&
  default_initializable<T> &&
  enable_view<T>;
Это позволяет писать обобщенный код, работающий с любыми представлениями:

C++
1
2
3
4
5
6
7
template <std::ranges::view V>
void process_view(V v) {
    // Можем быть уверены, что V - представление
    for (auto&& item : v) {
        use(item);
    }
}
Ошибки несоответствия типов теперь выявляются на ранних этапах компиляции с понятными сообщениями, а не в виде километровых ошибок шаблонов, как раньше.

Стратегии кэширования для дорогих вычислений



Несмотря на преимущества ленивых вычислений, иногда имеет смысл кэшировать результаты, особенно если операции дорогие или вызывают побочные эффекты. Я нередко использую такой подход для кэширования:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
template <std::ranges::view V>
class CachingView : public std::ranges::view_interface<CachingView<V>> {
private:
    V base_;
    mutable std::vector<std::ranges::range_value_t<V>> cache_;
    mutable bool initialized_ = false;
 
    void initialize() const {
        if (!initialized_) {
            cache_.assign(std::ranges::begin(base_), std::ranges::end(base_));
            initialized_ = true;
        }
    }
 
public:
    explicit CachingView(V base) : base_(std::move(base)) {}
 
    auto begin() const {
        initialize();
        return cache_.begin();
    }
 
    auto end() const {
        initialize();
        return cache_.end();
    }
};
Такое представление материализует результаты при первом доступе, а затем использует кэш для всех последующих итераций.

Механизм инвалидации и жизненный цикл



Работая с представлениями, критично понимать, как долго должны жить базовые данные. Поскольку представления не владеют данными, необходимо убедиться, что базовый диапазон остается действительным, пока используется представление.
Я однажды потратил целый день, отлаживая странную ошибку, которая оказалась классическим случаем dangling reference:

C++
1
2
3
4
5
6
7
8
9
std::ranges::view auto get_view() {
    std::vector<int> vec = {1, 2, 3, 4, 5}; // Создается локально
    return vec | std::views::filter([](int x) { return x % 2 == 0; }); // Опасно!
}
 
auto v = get_view(); // v ссылается на уничтоженный вектор
for (int x : v) { // Неопределенное поведение
    std::cout << x << ' ';
}
Правильный подход - обеспечить, чтобы базовый диапазон жил дольше представления:

C++
1
2
3
4
5
6
7
std::ranges::view auto apply_filter(std::ranges::range auto& r) {
    return r | std::views::filter([](int x) { return x % 2 == 0; });
}
 
std::vector<int> vec = {1, 2, 3, 4, 5}; // Создается на стеке
auto v = apply_filter(vec); // v ссылается на vec
// v действительно, пока действителен vec

Семантика перемещения и устранение копирования



Одна из прелестей представлений - они обычно тривиально перемещаемы. Это означает, что мы можем возвращать их из функций и передавать по значению практически без накладных расходов. Более того, стандарт C++20 гарантирует, что в определенных случаях копирование будет полностью устранено (copy elision), что делает работу с представлениями еще более эффективной:

C++
1
2
3
4
5
6
7
8
std::ranges::view auto create_pipeline() {
    // Никаких копий при возврате из функции
    return std::views::iota(1, 100) 
         | std::views::filter([](int n) { return n % 7 == 0; })
         | std::views::transform([](int n) { return n * n; });
}
 
auto pipeline = create_pipeline(); // Опять же, никаких копий
Именно эта комбинация легковесности и эффективности перемещения делает представления идеальными кандидатами для использования в качестве членов данных в пользовательских итераторах, о чем мы поговорим в следующих разделах.

Конвейерная обработка и оптимизация компилятора



Помимо всего вышесказанного, представления предлагают еще одно неочевидное преимущество - они существенно упрощают работу компилятора при оптимизации кода. Когда вы пишете цепочку трансформаций с представлениями, компилятор видит весь "конвейер" обработки целиком и может применить оптимизации, невозможные при использовании разрозненных алгоритмов.

Я проводил эксперименты с различными цепочками представлений и был поражен, насколько умно современные компиляторы могут оптимизировать такой код. В некоторых случаях весь конвейер трансформаций сворачивался до простого цикла, не уступающего по эффективности ручному коду:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Этот код с представлениями...
auto result = data | std::views::filter(pred1)
                  | std::views::transform(func1)
                  | std::views::filter(pred2)
                  | std::views::transform(func2);
 
// ...после оптимизации часто превращается в эквивалент:
for (const auto& item : data) {
    if (pred1(item)) {
        auto tmp1 = func1(item);
        if (pred2(tmp1)) {
            use(func2(tmp1));
        }
    }
}
Такая оптимизация возможна именно потому, что представления не скрывают детали своей реализации от компилятора, а наоборот - предоставляют всю информацию о том, что и как делается.

Кастомизация представлений через адаптеры



Одна из мощнейших возможностей представлений - это создание собственных адаптеров. Фактически, мы можем расширять библиотеку ranges своими компонентами, полностью совместимыми со стандартными.
Вот пример адаптера, который добавляет индексы к элементам диапазона:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
template <std::ranges::input_range V>
requires std::ranges::view<V>
class enumerate_view : public std::ranges::view_interface<enumerate_view<V>> {
private:
    V base_ = V();
    using inner_iterator = std::ranges::iterator_t<V>;
    
    class iterator {
    private:
        inner_iterator inner_;
        std::size_t index_ = 0;
    public:
        using value_type = std::pair<std::size_t, std::ranges::range_value_t<V>>;
        
        explicit iterator(inner_iterator it) : inner_(std::move(it)) {}
        
        value_type operator*() const {
            return {index_, *inner_};
        }
        
        iterator& operator++() {
            ++inner_;
            ++index_;
            return *this;
        }
        
        // Другие необходимые операторы...
    };
    
public:
    enumerate_view() = default;
    explicit enumerate_view(V base) : base_(std::move(base)) {}
    
    auto begin() { return iterator(std::ranges::begin(base_)); }
    auto end() { return iterator(std::ranges::end(base_)); }
};
 
// Удобный шаблон-переменная для создания экземпляров
template <std::ranges::range R>
auto enumerate(R&& r) {
    return enumerate_view(std::views::all(std::forward<R>(r)));
}
Этот адаптер можно использовать в цепочках представлений точно так же, как и стандартные:

C++
1
2
3
4
5
std::vector<std::string> words = {"яблоко", "банан", "киви"};
auto indexed_words = enumerate(words);
for (auto [idx, word] : indexed_words) {
    std::cout << idx << ": " << word << '\n';
}

Понимание внутренного устройства представлений



Для эффективной работы с представлениями важно понимать их внутреннее устройство. Большинство стандартных представлений реализованы как шаблонные классы, хранящие минимальный набор данных - обычно ссылку на базовый диапазон и, возможно, некоторые параметры адаптации. Возьмем для примера std::views::filter. Внутри он содержит:
1. Ссылку на базовый диапазон
2. Копию предиката (функционального объекта для фильтрации)

И это всё! Никаких промежуточных буферов, никаких лишних данных. При итерации filter_view просто проходит по базовому диапазону, применяет предикат к каждому элементу и пропускает те, которые не удовлетворяют условию. Я часто объясняю эту концепцию через аналогию с конвейером на заводе: каждое представление - это станция обработки, а элементы данных - детали, проходящие по конвейеру. Ни одна станция не хранит копии всех деталей - она обрабатывает их по одной, когда они проходят через нее.

Особенности работы с ссылками и указателями



Работая с представлениями, нужно учитывать нюансы, связанные с ссылками и указателями. Поскольку представления часто возвращают ссылки на элементы базового диапазона, вы должны быть внимательны при модификации данных через представление. Рассмотрим такой код:

C++
1
2
3
4
5
6
7
8
9
10
11
12
std::vector<int> nums = {1, 2, 3, 4, 5};
auto view = nums | std::views::filter([](int& x) { return x % 2 == 1; });
 
// Модификация через представление
for (int& x : view) {
    x *= 10;
}
 
// Выведет: 10, 2, 30, 4, 50
for (int x : nums) {
    std::cout << x << ' ';
}
Здесь мы модифицируем только нечетные числа через представление, и изменения отражаются в исходном векторе, потому что представление оперирует ссылками, а не копиями.

Однако если базовый диапазон предоставляет элементы по значению, а не по ссылке, то модификация через представление будет невозможна:

C++
1
2
3
4
5
6
7
std::list<std::string> words = {"hello", "world"};
auto copied = words | std::views::transform([](const std::string& s) { return s + "!"; });
 
// Ошибка компиляции: нельзя модифицировать временный объект
for (std::string& s : copied) {
    s = s.substr(0, 3);
}
В этом случае transform создает новые строки, и возвращаемые значения - это временные объекты, которые нельзя модифицировать.

Взаимодействие с другими фичами C++20



Представления в C++20 отлично взаимодействуют с другими новыми возможностями языка. Одна из самых мощных комбинаций - это использование представлений вместе с корутинами (coroutines).
Корутины позволяют создавать генераторы, которые производят последовательности значений "по запросу". Комбинируя их с представлениями, мы получаем мощный инструмент для работы с потенциально бесконечными последовательностями:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
generator<int> fibonacci() {
    int a = 0, b = 1;
    while (true) {
        co_yield a;
        auto next = a + b;
        a = b;
        b = next;
    }
}
 
void use_fibonacci() {
    auto fibs = fibonacci() | std::views::take(10);
    for (int x : fibs) {
        std::cout << x << ' ';  // Выведет первые 10 чисел Фибоначчи
    }
}
Без представлений было бы сложно работать с потенциально бесконечным генератором. С представлением take мы можем безопасно взять только нужное количество элементов.

Совместимость с ранними стандартами



Конечно, переход на C++20 не всегда возможен для существующих проектов. К счастью, есть библиотеки, которые предлагают аналогичную функциональность для более ранних стандартов:
  1. range-v3 - библиотека, на основе которой во многом была разработана стандартная библиотека ranges,
  2. Boost.Range - предлагает схожий функционал для проектов, уже использующих Boost,
  3. absl::Span от Google Abseil - более ограниченная альтернатива для простых случаев,

Я часто использую range-v3 для проектов на C++17, поскольку она предлагает практически идентичный интерфейс. Переход на стандартную библиотеку ranges при обновлении до C++20 становится тривиальным - достаточно заменить пространство имен.

Производительность и оптимизация



Многие разработчики опасаются, что такой высокоуровневый подход должен иметь серьезные накладные расходы. Мои бенчмарки показывают обратное - правильно использованные представления часто работают не хуже, а иногда и лучше ручного кода. Вот несколько ключевых моментов:

1. Современные компиляторы превосходно оптимизируют цепочки представлений.
2. Отсутствие промежуточных коллекций значительно снижает нагрузку на память.
3. Ленивые вычисления позволяют избежать лишней работы.

Однако есть и подводные камни. Например, многократный проход по одному и тому же представлению может быть неэффективен, если базовые операции дорогие. В таких случаях имеет смысл материализовать результат:

C++
1
2
3
4
5
6
7
8
// Представление с дорогими операциями
auto expensive_view = data | std::views::transform([](auto x) { 
    // Дорогая операция
    return compute_hash(x); 
});
 
// Если нужно многократно пройти по результатам, лучше материализовать
std::vector<hash_t> materialized(expensive_view.begin(), expensive_view.end());

Как добавить в QtCreator подсветку / подсказку итераторов auto?
class MyClass { public: MyClass(); int x; }; QVector&lt;MyClass&gt; data; for(auto&amp; it :...

Как с использованием итераторов в массиве чисел найти количество чисел, меньших за введенное?
Как при помощи итераторов в массиве чисел найти количество чисел, меньших за введенное?

Как сделать указатель на предыдущий элемент в массиве без итераторов?
тип MyData хранит в себе матрицу. myarray хранит в себе указатели на матрицы пробую сделать...

Не видит класс итераторов
Предметная область: Множество натуральных чисел, Реализованное через Хеш таблицы С цепочками. В...

сортировка с помошью итераторов
Дана последовательность действительных чисел. Необходимо используя алгоритм сортировки вставками...

Потоки и запоминание итераторов
Жду помощи... хочу, чтобы 2 потока запоминали итераторы, чтобы потом можно было свапнуть...

STL значения итераторов нивелируются при передаче в функцию
Ну и зачем тогда весь этот хвалёный STL? Получается в функции значения векторов не изменить так, а...

Перегрузка итераторов
Почему переполняется итератор vector&lt;char&gt;::iterator p = v.begin(); вот код : int _tmain (int...

Применение итераторов
Подскажите пожалуйста, в чем практичность итераторов, то бишь для чего нужны они в программах?

Использование потоковых итераторов
Вот код:#include&lt;iostream&gt; #include&lt;vector&gt; #include&lt;algorithm&gt; #include&lt;iterator&gt; using...

Двусвязный список с поддержкой итераторов
Привет, мальчики. Помогите сделать двусвязный список с поддержкой итераторов. Всем :kiss: ...

Сортировка с использованием итераторов
Добрый день! Я пытаюсь написать сортировку слиянием, используя итераторы. С итераторами раньше не...

Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
Access
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
Фото: Daniel Greenwood
kumehtar 13.11.2025
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru