1

Тема: wxStaticBoxSizer та контейнер vector

Намагаюся створити декілька динамічних wxStaticBox у вікні.
Для цього використовую вектори з вказівниками на wxStaticBox і wxStaticBoxSizer.
Вектор із wxStaticBox* створюється нормально,
а коли доходить справа до вектора із wxStaticBoxSizer* виникає помилка:

ASSERT INFO:
/usr/src/debug/wxwidgets/wxWidgets-3.2.2.1/src/common/sizer.cpp(2759): assert "box" failed in wxStaticBoxSizer(): wxStaticBoxSizer needs a static box

BACKTRACE:
(null)

Ось код:

map<wxString, tuple<wxString, wxString, wxString, wxString>> general_dict;
vector<wxStaticBox*> result_static_box_labels {nullptr};
vector<wxStaticBoxSizer*> result_static_box_sizers {nullptr};

void MainFrame::show_search_result(map<wxString,
                tuple<wxString, wxString, wxString, wxString>>& gen_dict)
{
    for (auto i: gen_dict)
    {
        wxString dict_name = get<0>(i);
        result_static_box_labels.push_back(new wxStaticBox(result_scrolled_window,
                                       wxID_ANY,
                                       dict_name,
                                       wxDefaultPosition, wxDefaultSize));
    }

    for (auto i: result_static_box_labels)
    {
        result_static_box_sizers.push_back(new wxStaticBoxSizer(i, wxVERTICAL));
    }
}

Компілятор працює без помилок, помилка з'являється при спробі запуску програми.

2 Востаннє редагувалося koala (18.09.2023 17:01:56)

Re: wxStaticBoxSizer та контейнер vector

vector<wxStaticBox*> result_static_box_labels {nullptr};

означає "вектор з 1 посилання на wxStaticBox, що дорівнює nullptr", а не "порожній вектор". І цей nullptr ви ніде не видаляєте.

P.S. Щоб зрозуміти це, я відкрив https://github.com/wxWidgets/wxWidgets/ … /sizer.cpp і виявив там

wxASSERT_MSG( box, wxT("wxStaticBoxSizer needs a static box") );

Отже, assert перевіряє лише параметр box як булевий, а значить, там передається nullptr.

Подякували: Teg Miles, leofun012

3

Re: wxStaticBoxSizer та контейнер vector

koala написав:
vector<wxStaticBox*> result_static_box_labels {nullptr};

означає "вектор з 1 посилання на wxStaticBox*, що дорівнює nullptr", а не "порожній вектор". І цей nullptr ви ніде не видаляєте.

P.S. Щоб зрозуміти це, я відкрив https://github.com/wxWidgets/wxWidgets/ … /sizer.cpp і виявив там

wxASSERT_MSG( box, wxT("wxStaticBoxSizer needs a static box") );

Отже, assert перевіряє лише параметр box як булевий, а значить, там передається nullptr.

Тобто контейнери із вказівниками краще не ініціалізувати з nullptr, на відміну від окремої змінної?

4 Востаннє редагувалося koala (18.09.2023 16:56:41)

Re: wxStaticBoxSizer та контейнер vector

Тобто краще ініціалізувати те, що треба занулити, {}, а не {nullptr}. Для вказівників це означає якраз nullptr.

Подякували: leofun01, Teg Miles2

5 Востаннє редагувалося wander (18.09.2023 21:41:49)

Re: wxStaticBoxSizer та контейнер vector

Ініціалізація форми { arg1, arg2, ... } - це так звана агрегатна ініціалізація. Але, якщо тип Т - це тип класу, який має конструктор з std::initializer_list'ом, то викличеться саме він. У вашому випадку саме це і відбудеться, адже std::vector має такий конструктор:

constexpr vector( std::initializer_list<T> init,
                  const Allocator& alloc = Allocator() );

Відповідно він і викликається:

std::vector<int> v1{5}; // ініціалізація вектора з одного елемента зі значенням `5`
std::vector<int> v1(5); // ініціалізація вектора з п'яти елементів зі значенням `0`
Vitaliy_Danmer написав:

Тобто контейнери із вказівниками краще не ініціалізувати з nullptr, на відміну від окремої змінної?

Їх варто ініціалізовувати, але лише у тому випадку, коли ви заздалегідь вказуєте розмір, а потім ручками (циклом) ітеруєтесь по вектору для модифікації даних. Ви ж викликаєте push_back, який просто запише новий елемент у сам кінець вектора збільшивши його розмір на "1".

Подякували: Teg Miles, mimik2

6

Re: wxStaticBoxSizer та контейнер vector

push_back і заздалегідь встановлений розмір можна "сумістити" за допомогою std::vector::reserve:

using GeneralDict = std::map<wxString, tuple<wxString, wxString, wxString, wxString>>; 
GeneralDict general_dict;
std::vector<wxStaticBox*> result_static_box_labels {};
std::vector<wxStaticBoxSizer*> result_static_box_sizers {};

void MainFrame::show_search_result(GeneralDict & gen_dict)
{
    result_static_box_labels.reserve(gen_dict.size());
    result_static_box_sizers.reserve(gen_dict.size());
    for (auto& i: gen_dict)
    {
        wxString dict_name = get<0>(i);
        wxStaticBox *box = new wxStaticBox(result_scrolled_window,
                                       wxID_ANY,
                                       dict_name,
                                       wxDefaultPosition, wxDefaultSize);
        result_static_box_labels.push_back(box);
        result_static_box_sizers.push_back(new wxStaticBoxSizer(box, wxVERTICAL));
    }
}

7

Re: wxStaticBoxSizer та контейнер vector

Можна, проте це немає жодного відношення до ініціалізації. А, наявність чи відсутність пустих {} - не змінить нічого, адже в обох випадках викличеться конструктор за замовчуванням.

Подякували: koala1

8

Re: wxStaticBoxSizer та контейнер vector

wander написав:

А, наявність чи відсутність пустих {} - не змінить нічого, адже в обох випадках викличеться конструктор за замовчуванням.

Змінить читаність коду. Звісно, оскільки тут назва типу досить складна, то ніби очевидно, що буде якийсь конструктор і якась ініціалізація; але в ситуаціях на кшталт

//у заголовку
using ImagePtr = std::shared_ptr<MyImageType>;
using WindowPtr = wxWindow *;
...
//десь в іншому файлі
ImagePtr img {};
WindowPtr window {};
//або
ImagePtr img; //default constructor зробить це nullptr, як і вище
WindowPtr window; //а тут неініціалізоване

можна випадково не ініціалізувати змінну. Тому гарною звичкою буде ініціалізувати все, що має бути порожнім, {}, а неініціалізоване - пропускати. Утім, це справа смаку.

9 Востаннє редагувалося wander (18.09.2023 22:28:25)

Re: wxStaticBoxSizer та контейнер vector

koala написав:

Змінить читаність коду. Тому гарною звичкою буде ініціалізувати все, що має бути порожнім, {}, а неініціалізоване - пропускати.

Згоден, проте нмсд краще просто ось так:

//у заголовку
using ImagePtr = std::shared_ptr<MyImageType>;
using WindowPtr = wxWindow *;

ніколи не писати. Можна по-різному зробити краще і навіть зберегти необхідну поведінку, але так, це вже, напевно, більш вузько спрямовані настанови (guidelines) по написанню коду :)

10

Re: wxStaticBoxSizer та контейнер vector

Та зараз діти C на оцьому вивчають: https://github.com/cs50/libcs50
Бачите, який красивий там string?

11

Re: wxStaticBoxSizer та контейнер vector

Ох, так, ця бібліотека з cs50.. Це у нас "specific solution to a specific problem" :D
Хоча, я гадаю, їм можна пробачити. Загалом так, краще зайвий раз ініціалізувати ніж потім шукати, що не так.
Ще хорошим тоном було б увімкнути попередження, зараз компілятори досить розумні:

extern void foo(int*);
int main() {
    int* p;
    foo(p);
}

<source>:8:9: warning: variable 'p' is uninitialized when used here [-Wuninitialized]
    foo(p);
        ^
<source>:7:11: note: initialize the variable 'p' to silence this warning
    int* p;
          ^
           = nullptr
1 warning generated.

Подякували: koala, mimik2