powered by simpleCommunicator - 2.0.59     © 2025 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / C++ [игнор отключен] [закрыт для гостей] / Пятничные контейнеры
32 сообщений из 32, показаны все 2 страниц
Пятничные контейнеры
    #39592265
Фотография CEMb
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Решил пособирать best practics

Переписываю старый код на с++17
Код работает с контейнерами.
Есть несколько векторов с объектами.
В процессе обработки одного вектора, код может начать обработку другого вектора и в ней удалить элемент первого.
Чтобы ничего плохого не случилось, у меня есть отдельные три вектора на добавление, удаление и перемещение объектов.
И это немного неудобно, да и в коде выглядит криво.
Поэтому вопрос, как лучше делать такой код:

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
// идёт итерация по вектору
for (vector<Obj*>::iterator it = m_Obj.begin(); it < m_Obj.end(); it++)
{
    (*it)->process();
}

// ... far-far-galaxy

Obj::foo()
{
    // а тут я забыл, что я в итерации, да и могу быть и не в итерации.
    if (condition)
    {
        vector<Obj*>::iterator it = m_Obj.begin() + 0; // ну, какой-то там элемент из вектора...
        m_Obj.erase(it); // !!!
    }
}



в общем случае вектор сдвинется и итератор из верхнего кода станет невалидным, мы получим исключение
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39592267
Фотография CEMb
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Код: plaintext
1.
Obj::foo()

имелось ввиду
Код: plaintext
1.
Obj::process()


если можно, исправьте в первом сообщении, а это потрите :)
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39592276
Фотография Cerebrum
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
режим перфекциониста ON

Код: plaintext
1.
for (vector<Obj*>::iterator it = m_Obj.begin(); it < m_Obj.end(); it++)


обычно пишут:
Код: plaintext
1.
for (auto it = m_Obj.begin(); it != m_Obj.end(); ++it)


еще лучше:
Код: plaintext
1.
for (auto it = m_Obj.begin(), end = m_Obj.end(); it != end; ++it)


короче:
Код: plaintext
1.
for (auto it: m_Obj)


режим перфекциониста OFF
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39592287
alex_k
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
CEMb,

возможно, нужен std::list. если не нужен произвольный доступ по индексу и нет задачи укладываться в кэш процессора.
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39592294
Фотография CEMb
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Cerebrumрежим перфекциониста ON
Вот как раз перфекционисты пишут
Код: plaintext
1.
vector<Obj*>::iterator it 

или, ещё лучше, используют using и договорённую нотацию, потому что взгляд человеком на auto-переменную для определения типа требует больше процессорного времени :)

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

режим перфекциониста OFF
да и просто самому неудобно читать
ладно когда так:
Код: plaintext
1.
for (auto it: m_Obj)

или так:
Код: plaintext
1.
for (auto obj: m_Obj)

ты сразу видишь, что там.
Но вот когда так:
Код: plaintext
1.
auto a = foo();

где-то недавно попадался чудный код, типа
Код: plaintext
1.
auto i = foo();

человек ждал int на выходе, вычитал из него и проверял его на "меньше нуля", чтобы использовать, как индекс в массиве, а foo возвращало unsigned int. Ну и человек пошёл задавать вопросы на форум, почему у него всё ломается...
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39592295
Фотография CEMb
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alex_kи нет задачи укладываться в кэш процессорану вот есть такая задача :(
т.е. нужно, чтобы по массиву обработчики бежали быстро

ладно, буду думать :)
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39592343
Фотография Anatoly Moskovsky
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
CEMbт.е. нужно, чтобы по массиву обработчики бежали быстро
Удаление из вектора это по определению не быстро ))
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39592345
Фотография Anatoly Moskovsky
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Кстати ничего не мешает std::list хранить в непрерывном участке памяти (через соответствующий аллокатор)
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39592454
Фотография CEMb
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Anatoly MoskovskyУдаление из вектора это по определению не быстро ))
Если не часто это делать, то не сильно напрягает.
Но вот небезопасно - вот в этом у меня проблема.

Я вообще думал, это довольно распространённое явление(add/del в массив во время итераций), и уже его как-то все побороли, один я отстал от жизни... о_о

Ещё я эту проблему решал так: при удалении элементов, не удалял их а помечал, как удалённые. Удалял потом отдельным проходом. Тоже не айс.

Anatoly MoskovskyКстати ничего не мешает std::list хранить в непрерывном участке памяти (через соответствующий аллокатор)Но там же надо следить за размеров выделенной памяти?
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39592581
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
CEMbAnatoly MoskovskyУдаление из вектора это по определению не быстро ))
Если не часто это делать, то не сильно напрягает.
Но вот небезопасно - вот в этом у меня проблема.
"вниз цикл пустить" не предлагать?

CEMbЕщё я эту проблему решал так: при удалении элементов, не удалял их а помечал, как удалённые. Удалял потом отдельным проходом. Тоже не айс.вполне норм, не вижу причин комплексовать
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39592739
Фотография CEMb
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan)"вниз цикл пустить" не предлагать?
это как? с end до begin?

kealon(Ruslan)вполне норм, не вижу причин комплексовать
не, не норм, это ж дополнительно ещё раз прогнать весь список.
но второй мой вариант был ещё хуже: собирать всех удаленцев в один список (например, вектор указателей), а потом по нему удалять.

тем временем, я с утра придумал такое простое, но корявое решение:
1. перед циклом запоминаем _Myend вектора
2. заводим индекс
3. после каждого прохода проверяем, изменился ли _Myend? Если да, то наш вектор перекочевал куда-то. Берём индекс из пункта 2 и считаем итератор заново: begin() + idx
это всё отлично работает на добавлении в хвост .

(я хотел сделать это ещё проще, перехватив _Orphan_All())

А вот с удалением всё несколько сложнее :)

Вообще можно пойти в исходники вектора и посмотреть, как проверяется валидность итераторов (_Myfirst <= it <= _Mylast), но проблемы могут быть следующие:
1. insert, таким образом, мы можем на одном и том же элементе вызвать process() два раза
2. erase - тоже один элемент останется не обработанным
т.е. даже если получать элементы по индексу из массива, а не по итератору, всё равно имеем проблемы.

И в результате получается самый устойчивый вариант: хранить добавляемые, удаляемые, перемещаемые элементы в отдельных векторах и потом их отдельным ходом добавлять, удалять и перемещать. Но это выглядит громоздко: плюс 2-3 вектора.

Может надо отказаться от итераций вообще? Придумать какой-нибудь механизм сообщений? В окнах же так, если я по событию нажатия в окне на одной кнопке, удаляю соседнюю, следующую по Z-order-у, ничего же не падает
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39592789
Фотография Anatoly Moskovsky
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
CEMb,

Работайте не с итераторами, а с индексами.
Если если текущий элемент удален, то не инкрементировать индекс цикла. И все дела.
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39592880
Фотография CEMb
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Anatoly MoskovskyЕсли если текущий элемент удален, то не инкрементировать индекс цикла. И все дела.
В том и проблема, что мы не знаем, удалён ли элемент. Так бы и проблем не было.

В общем, надо подвести итог по этой теме
C++ в данном случае не делает ничего лишнего и делает всё быстро, как и задумывалось.
При этом можно просто добавлять контроль за итераторами, если надо, причём для разных ситуаций (к примеру, я буду только добавлять в хвост) можно оптимизировать этот контроль.
я просто напишу простенький класс над вектором, с теми же методами, push, emplace, erase и т.д. который будет кешировать входящие значения, и добавлю метод refresh, который будет скидывать/применять временные вектора на постоянное хранилище. Снаружи всё будет выглядеть ровно как обычный вектор.

Как вариант, можно было бы переопределить операторы ++, +, --, - для итератора и там проверять, что он не вышел за границы. Тут есть один момент, который упрощает ситуацию - функции, типа _Orphan_All зовутся напрямую из системной библиотеки у меня на Win7, msvcp120.dll, внутрь я сильно смотрел, но главное: они как-то умеют находить и сбрасывать все итераторы. Как только вектор переехал на новое место, или часть его сдвинулась - все итераторы, указывающие наружу, или те, под которыми память сдвинулась - сбрасываются.
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39592994
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
CEMb,
std::reverse_iterator
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39593054
Фотография CEMb
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan)std::reverse_iterator
так он тоже не спасает от переноса вектора на новое место :(

Btw, меня-таки припёрло, я собираюсь попробовать сделать абстрактный UI, из чего эта тема и выросла :)
К примеру, идёт обработка сообщения, и обработчики "окон" порождают/убивают/переносят другие окна, нарушая консистентность.
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39593079
Фотография CEMb
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
А вот ещё вопросы:
- как сделать move одного вектора в хвост другого? Именно move, чтобы перемещаемый вектор стал нулевой длины?
т.е.
Код: plaintext
1.
insert(a.end(), make_move_iterator(b.begin()), make_move_iterator(b.end()));

делает move по элементам, но сам вектор не трогает.

- нужен ли такой перенос вектора? :)
т.е. по идее это должно быть довольно быстро, по сравнению с переносом по элементам:
1. зарезервировать новую длину для dest
2. перенести source целиком по адресу хвоста dest
но как оно на самом деле?
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39593100
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
CEMbkealon(Ruslan)std::reverse_iterator
так он тоже не спасает от переноса вектора на новое место :( тынц
CEMbBtw, меня-таки припёрло, я собираюсь попробовать сделать абстрактный UI, из чего эта тема и выросла :)
К примеру, идёт обработка сообщения, и обработчики "окон" порождают/убивают/переносят другие окна, нарушая консистентность.В случае произвольного удаления, а не по ходу, итераторами пользоваться в любом случае нельзя.

С другой стороны, если у вас нет доступа по проивольному индексу - зачем вам Vector?

Если хотите нормально сделать, делайте сами класс под свои нужды, со своими плюшками. Костыли из STL для ширпотреба сделаны.
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39593169
Фотография CEMb
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan)В случае произвольного удаленияда, подразумевалось произвольное удаление.
kealon(Ruslan)итераторами пользоваться в любом случае нельзя.вот у меня сомнения.
Во-первых, я посмотрел внутрь вектора под дебагом. Он при фатальных изменениях вектора как-то знает, где у него все итераторы, и умеет их инвалидировать по надобности.
Во-вторых, такая ситуация: три (прямых) итератора, указывают на 1, 3 и 5 элементы вектора. Делаем второму После этого первый валидный, а третий нет. Т.е. вектор, перенося хвост, заинвалидейтил только два итератора, оставив разумно первый в валидном состоянии.
Т.е. по идее, я (код) могу понять, куда двинулся вектор или кусок вектора, и переместить за ним нужные итераторы.

kealon(Ruslan)Если хотите нормально сделать, делайте сами класс под свои нужды, со своими плюшками.ага, это самый правильный вариант, получается.
Я стал делать это, используя дополнительные векторы для удаляемых/добавляемых элементов. Но вот есть мысль, что можно было бы сделать одним вектором и с проверкой итераторов на валидность.
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39593230
YesSql
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
CEMbА вот ещё вопросы:
- как сделать move одного вектора в хвост другого? Именно move, чтобы перемещаемый вектор стал нулевой длины?
т.е.
Код: plaintext
1.
insert(a.end(), make_move_iterator(b.begin()), make_move_iterator(b.end()));

делает move по элементам, но сам вектор не трогает.

- нужен ли такой перенос вектора? :)
т.е. по идее это должно быть довольно быстро, по сравнению с переносом по элементам:
1. зарезервировать новую длину для dest
2. перенести source целиком по адресу хвоста dest
но как оно на самом деле?
На самом деле применяется оператор std::move к каждому переносимому элементу. а будет это перемещение или копирование зависит от элемента.

то что ты хочешь это что то типа
Код: plaintext
1.
template<typename T> size_type std::vector<T>::append(std::vector<T> &&)


Это в общем-то тоже самое что и
Код: plaintext
1.
2.
3.
a.reserve(a.size()+b.size());
std::move(b.begin(), b.end(), std::back_inserter(a));
b.clear();
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39593236
Фотография CEMb
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
YesSqlто что ты хочешь это что то типада, и я ничего такого не нашёл

YesSqlЭто в общем-то тоже самое что инеее, там move делается на каждом элементе, идут итерации. А хочется просто одним куском перенести второй вектор в хвост первого. Это ничему не противоречит, так как там просто массив указателей, memcpy, очень быстро.
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39593238
YesSql
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
YesSql,

имелось ввиду
Код: plaintext
1.
a.reserve(b.size());
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39593246
YesSql
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
CEMbYesSqlто что ты хочешь это что то типада, и я ничего такого не нашёл

YesSqlЭто в общем-то тоже самое что инеее, там move делается на каждом элементе, идут итерации. А хочется просто одним куском перенести второй вектор в хвост первого. Это ничему не противоречит, так как там просто массив указателей, memcpy, очень быстро.
одним куском нельзя. Как я уже сказал выше. зависит от элемента вектора - можно его переность или нельзя.
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39593263
Фотография CEMb
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
YesSqlКак я уже сказал выше. зависит от элемента вектора - можно его переность или нельзя.а в каких случаях нельзя перенести? Если мы перенесём массив указателей, вроде бы ничего для элементов не поменяется?
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39593276
YesSql
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
CEMbYesSqlКак я уже сказал выше. зависит от элемента вектора - можно его переность или нельзя.а в каких случаях нельзя перенести? Если мы перенесём массив указателей, вроде бы ничего для элементов не поменяется?

Это твой очень частный случай. Врят-ли его внесут в стандарт. В общем случае только сам элемент знает как его можно перенести. И возможно ли это вообще
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39593330
Фотография CEMb
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
YesSqlЭто твой очень частный случай. Врят-ли его внесут в стандарт.не надо ничего вносить в стандарт :)
YesSqlВ общем случае только сам элемент знает как его можно перенести.Не, я не про элементы, я про вектор, как контейнер. Итераторы - это указатели, и они лежат в памяти один за другим. А вот сами элементы могут лежать где угодно и как угодно и быть чем угодно. Но вектор это кусок непрерывной памяти с указателями. Как я понимаю, этот кусок памяти можно перенести безболезненно для любого типа элементов?
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39593418
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
CEMbНо вектор это кусок непрерывной памяти с указателями. Как я понимаю, этот кусок памяти можно перенести безболезненно для любого типа элементов? можно если осторожно, со SMART Po можно залететь
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39593473
YesSql
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
CEMb Не, я не про элементы, я про вектор, как контейнер. Итераторы - это указатели, и они лежат в памяти один за другим. А вот сами элементы могут лежать где угодно и как угодно и быть чем угодно. Но вектор это кусок непрерывной памяти с указателями. Как я понимаю, этот кусок памяти можно перенести безболезненно для любого типа элементов?

это твой частный случай то библиотека сдесь непричём.
Код: plaintext
1.
2.
3.
4.
5.
6.
 std::vector<int> a {1,2,3,4,5,6}, b {9,10,11};
 
 size_t a_len = a.size(), b_len = b.size();
 a.resize(a_len+b_len);
 memcpy(&(a[a_len]), &(b[0]), b_len*sizeof(b[0]));
 b.clear();
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39593915
Фотография CEMb
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
YesSqlэто твой частный случай то библиотека сдесь непричём.посмотрел ещё раз внутрь. И ничего не понял :)
взял этот пример, встал брекпоинтом перед memcpy. Во-первых, нельзя обращаться по адресу элемента через индекс. Но это ладно. Я стал смотреть значения адресов a.begin() и b.begin() через quitview. Они оказались одинаковыми! Нет, я не глючу. При запуске quickview студия прописывала адрес итератора в одно и то же место. Это какая-то, видимо, адова оптимизация. После этого я перестал там пытаться разобраться, как и что лежит в памяти, потому что там квантовая механика уже, если ты посмотрел на итератор, то он изменил своё состояние
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39594017
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
CEMb,

да всё просто, память просто может перелоцироваться и указатель в итераторе будет указывать на освобождённую область
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39594033
egorych
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
CEMbВо-первых, нельзя обращаться по адресу элемента через индекс.это почему?
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39594047
YesSql
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
CEMbВо-первых, нельзя обращаться по адресу элемента через индекс.

В твоём частном случае можно.
Код: plaintext
1.
2.
3.
4.
5.
6.
  reference
      operator[](size_type __n) _GLIBCXX_NOEXCEPT
      {
	__glibcxx_requires_subscript(__n);
	return *(this->_M_impl._M_start + __n);
      }



для этого даже ввели метод data() в c++11
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
     /**
       *   Returns a pointer such that [data(), data() + size()) is a valid
       *   range.  For a non-empty %vector, data() == &front().
       */
      _Tp*
      data() _GLIBCXX_NOEXCEPT
      { return _M_data_ptr(this->_M_impl._M_start); }



FYI
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
      void
      _M_create_storage(size_t __n)
      {
	this->_M_impl._M_start = this->_M_allocate(__n);
	this->_M_impl._M_finish = this->_M_impl._M_start;
	this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
      }
    };
...
Рейтинг: 0 / 0
Пятничные контейнеры
    #39596502
Фотография CEMb
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
egorychэто почему?ну вот по той странной ситуации, когда у меня обращение к begin() что-то меняло в памяти.
YesSqlFYIага, спасибо, посмотрю в эту сторону
...
Рейтинг: 0 / 0
32 сообщений из 32, показаны все 2 страниц
Форумы / C++ [игнор отключен] [закрыт для гостей] / Пятничные контейнеры
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]