Удаление объекта из вектора во время цикла проходящего по этому вектору

372
17 января 2018, 17:21

Для разминки в с++ пишу небольшую ООП надстройку над WinApi, для того чтобы можно было парой строчек кода создавать окна и проводить необходимые основные манипуляции (такие как смена текста, размеров, установка событий на элементах управления и так далее). Планировалось что ее использование будет примерно таким:

WqWindow::WqBegin();
WqWindow w;
WqButton b(&w);
WqTextBox tb(&w);
tb.SetPosition(WqPosition(50, 10))
    ->SetSize(WqSize(200, 25))
    ->SetText("");
b.SetText("Cool button")
    ->SetSize(WqSize(150, 25))
    ->SetPosition(WqPosition(50, 50))
    ->SetAcnhor(WqControlAnchor(false, false, true, false))
    ->SetOnClick([&b, &tb, &w]() { cout << "Clicked!" << endl; });
w.SetTitle("Cool window")
    ->SetSize(WqSize(400, 400))
    ->ClosesProgram(false)
    ->SetOnClose([]() { cout << "Closed!" << endl; return true; })
    ->Show();
WqWindow::WqEnd();

Принцип примерно такой - при создании элемента управления указатель на него добавляется в вектор элементов объекта окна (WqWindow) а при удалении, в деструкторе, этот элемент из вектора убирается. Примерно вот таким образом

//Убираем из списка элементов управления окна
this->window_->controls_.erase(std::remove(this->window_->controls_.begin(), this->window_->controls_.end(), this), this->window_->controls_.end());

Сами события кнопок и прочих элементов обрабатываются в оконной процедуре. Принцип примерно следующий : получаем хендл окна вызвавшего событие (HWND), используя пользовательский указатель (который присвоили при создании WqWindow окна) получаем связанный объект WqWindow и обращаемся к вектору элементов управления. Проходим по ним в цикле, и вызываем лямбда-функции этих элементов. Выглядит это примерно так:

    case WM_COMMAND:
        if (wqWindow && !wqWindow->controls_.empty()) {
            const std::vector<WqControl*> safeControlPointers(wqWindow->controls_);
            for (WqControl * control : safeControlPointers) 
            {
                if (control->initialized_)
                {
                    if (HIWORD(wParam) == EN_CHANGE) {
                        if (control && control->ControlClassName() == "Edit" && control->GetHWND() == (HWND)(lParam)) {
                            WqTextBox * pTextBox = ((WqTextBox*)control);
                            if (pTextBox->onChanged_) {
                                pTextBox->onChanged_();
                            }
                        }
                    }
                    else {
                        if (control->ControlClassName() == "Button" && control->GetHWND() == (HWND)(lParam)) {
                            WqButton * pButton = ((WqButton*)control);
                            if (pButton->onClick_) {
                                pButton->onClick_();
                            }
                        }
                    }
                }
            }
        }
        break;

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

->SetOnClick([&b, &tb, &w]() { tb.~WqTextBox(); });

На этот случай я как раз и добавил переменную safeControlPointers, чтобы при проходе по вектору мы работали как-бы с копией вектора, а не тем вектором размер которого будет меняться. Вроде должно было быть все в порядке, но все равно возникала ошибка при выполнении (при нажатии на кнопку). Дело в том что в копии вектора оставался указатель на объект который как-бы удален. НЕ ЗНАЮ КАК, но мне помогло следующее - я объявил в WqControl флаг control->initialized_, который в конструкторе становился true а деструкторе устанавливался в flase. В цикле вы наверное заметили проверку

if (control->initialized_)

Именно благодаря этому программа не ломалась при нажатии на кнопку. Я до конца так и не понял почему (ведь объект уничтожается, удаляется из памяти, соответственно доступ к каким либо его членам невозможен). Буду рад, если поясните как это возможно. Ну на этом я не остановился, и решил попробовать создать TextBox (который потом задумал удалить при клике) не в стеке а в куче (то есть при помощи оператора new) а затем вызвать delete при клике. Вот тут все окончательно сломалось. И не помогают никакие проверки (на пустоту указателя и прочие).

Вопрос : Как реализовать подобный механизм, чтобы при клике была возможность удалять другие элементы? Как правильно следует подходить к этому? За раннее спасибо.

Answer 1

Всем спасибо за советы, похоже проблема решена. Я просто избавился от цикла в обработке сообщения WM_COMMAND. У каждого элемента управления ведь есть пользовательский указатель на объект (содержащий все необходимые функции обратного вызова). Почему-то забыл о нем. Я просто получаю хендл эл-мента управления а затем из него достаю этот указатель, и вызываю нужные функции. Теперь это выглядит так:

    case WM_COMMAND:
        if (lParam) {
            WqControl * wqControl = (WqControl*)GetWindowLongPtr((HWND)(lParam), GWLP_USERDATA);
            if (wqControl) {
                if(wqControl->ControlClassName() == "Edit"){
                    WqTextBox * pTextBox = ((WqTextBox*)wqControl);
                    if (pTextBox->onChanged_) {
                        pTextBox->onChanged_();
                    }
                }
                else if (wqControl->ControlClassName() == "Button") {
                    WqButton * pButton = ((WqButton*)wqControl);
                    if (pButton->onClick_) {
                        pButton->onClick_();
                    }
                }
            }
        }
        break;

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

READ ALSO
Синхронизация процессов

Синхронизация процессов

Подскажите, как сделать синхронизацию процессов? Через fork создаю 2 дочерних процесса, и мне нужно читать файл, чтобы каждый процесс читал...

280
Почему не работает шаблон?

Почему не работает шаблон?

Мне нужно написать шаблон, который меняет местами диагонали матрицыПри компиляции выдает ошибку и не знаю как исправить

283
сортировка изображений

сортировка изображений

Сравнил попарно все изображения - получается матрица чисел (для удобства сжаты до 0-254) N x N, как теперь упорядочить чтобы похожие были рядом...

317
gdb отладка многопоточного приложения

gdb отладка многопоточного приложения

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

410