To warm up in c ++, I write a small OOP add-on over WinApi so that you can create windows and carry out the necessary basic manipulations (such as changing text, sizes, setting events on controls, etc.) with a couple of lines of code. It was planned that its use would be something like this:

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(); 

The principle is approximately the same: when creating a control, a pointer to it is added to the vector of window object elements (WqWindow) and when removed, in the destructor, this element is removed from the vector. Something like this

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

The events of the buttons and other elements are handled in the window procedure. The principle is the following: we get the handle of the window that caused the event (HWND), using the user pointer (which was assigned when creating the WqWindow window), we get the associated WqWindow object and refer to the control vector. We pass through them in a loop, and call the lambda functions of these elements. It looks like this:

  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; 

And then I thought - what if the user of this library wants to delete (by calling the destructor) some control in the lamba expression, for example like this:

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

In this case, I just added the safeControlPointers variable, so that when we walk through the vector, we work as if with a copy of the vector, and not with that vector, the size of which will change. It was supposed to be all right, but still there was an error during execution (when pressing the button). The fact is that in the copy of the vector there was a pointer to the object which, as it were, was deleted. I DON'T KNOW HOW, but the following helped me - I declared the control-> initialized_ flag in WqControl, which in the constructor became true and the destructor was set to flase. In the loop, you probably noticed a check.

 if (control->initialized_) 

Due to this, the program did not break when you click on the button. I did not fully understand why (after all, an object is destroyed, deleted from memory, and therefore access to any of its members is impossible). I would be glad if you explain how this is possible. Well, I did not stop there, and decided to try to create a TextBox (which I then decided to delete when clicked) not on the stack but on the heap (that is, using the new operator) and then call delete when clicked. Here everything finally broke. And no checks help (for pointer emptiness and others).

Question : How to implement a similar mechanism so that when you click there is the ability to delete other elements? How to properly approach this? Thanks in advance.

  • DestroyWindow call when you destroy an object? - LLENN
  • And why do you keep this vector at all? The list of child windows is already stored in the window. They can be obtained for example through EnumChildWindows . - VTT
  • "because the object is destroyed, deleted from memory, respectively, access to any of its members is impossible." How did you imagine "deleting an object from memory"? Some small nuclear explosion that burns to memory the memory in which the object was previously located? Memory does not go anywhere. And the data in it, too, hang out, until someone overwrites them. But access to this memory is prohibited - the behavior is not defined. And the fact that your code "works" is just lucky for you so far. - AnT
  • VTT, you propose not to store an array of objects, but in a window procedure to go through child windows (controls) using EnumChildWindows and use a custom pointer to access the object containing the template. call object? But the destructor destroys the window (that is, it calls DestroyWindow for the control), and accordingly, a situation may arise in which the passage through the child elements of the window fails if one of these child windows is deleted during this passage. Or not? - Alex Nem
  • In my opinion, the situation with deleting (and adding) windows during the enumeration is handled correctly. In any case, you do not run the risk of running into the dereferencing of a broken pointer. - VTT

1 answer 1

Thank you all for the advice, it seems the problem has been solved. I just got rid of the loop in processing the WM_COMMAND message. After all, each control has a custom pointer to the object (containing all the necessary callback functions). For some reason I forgot about him. I just get the handle of the control e-ment and then I pull out this pointer from it and call the necessary functions. Now it looks like this:

  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; 

As a result, the cycle does not start, and there are no errors when deleting from a vector. Yes, and keep a list of pointers really, it turns out, not necessarily. At least for now it seems that way ..