Сбой при попытке создать экземпляр дочернего класса

У меня есть _Object родительский класс и _Button дочерний класс. Вот мой ужасающий код:

class _Object {
protected:
    SDL_Texture* texture;
    const char* image_path;
    static uint64_t count; /* 18,446,744,073,709,551,615 objects ought to be enough for anybody */
    uint_least32_t group;
    static std::set<int_fast64_t> z_order_set;
    int_fast64_t z_order;
    int64_t x;
    int64_t y;
    uint8_t alpha;
    uint16_t rot;
    float scale;
    static int_fast64_t uniquify_z_order(int_fast64_t& z_order) {
        if (z_order >= 0) {
            while (z_order_set.find(z_order) != z_order_set.end()) {
                z_order++;
                if (z_order == INT_FAST64_MAX) {
                    goto EXCEPTION;
                }
            }
        } else {
            while (z_order_set.find(z_order) != z_order_set.end()) {
                z_order--;
                if (z_order == INT_FAST64_MIN) {
                    goto EXCEPTION;
                }
            }
        }
        return z_order;
EXCEPTION:
        z_order = 0;
        return 0;
    }
public:
    _Object() = delete;
    _Object(SDL_Renderer* rend, int_fast64_t z_order, const char* image, int x, int y) {
        _constructor(rend, z_order, image, x, y, ((uint8_t)255), ((uint16_t)0), 100.0f);
    }
    _Object(SDL_Renderer* rend, int_fast64_t z_order, const char* image, int x, int y, uint8_t alpha, uint16_t rot, float scale) {
        _constructor(rend, z_order, image, x, y, alpha, rot, scale);
    }
    _Object(SDL_Renderer* rend, _Object* obj) {
        _constructor(rend, obj->z_order, obj->image_path, obj->x, obj->y, obj->alpha, obj->rot, obj->scale);
    }
    ~_Object() {
        SDL_DestroyTexture(this->texture);
    }
    /* Constructor */
    void _constructor(SDL_Renderer* rend, int_fast64_t z_order, const char* image_path, int x, int y, uint8_t alpha, uint16_t rot, float scale) {
        if (z_order != 0) {
            if (z_order_set.find(z_order) != z_order_set.end()) {
                stacktrace(module::warning, "z_order (%lld) is already in use. New unique z_order: %lld", z_order, uniquify_z_order(z_order));
                if (z_order == 0) {
                    stacktrace(module::error, "No free z_order left. Object discarded.");
                    this->z_order_set.erase(z_order);
                    throw OUT_OF_Z_ORDER;
                }
            }
            this->z_order_set.insert(z_order);
            this->z_order = z_order;
        } else {
            stacktrace(module::error, "Attempted to use zero as a z_order (reserved for player). Object discarded.");
            throw OUT_OF_Z_ORDER;
        }
        this->texture = IMG_LoadTexture(rend, image_path);
        if (!this->texture) {
            stacktrace(module::error, "Couldn't load \"%s\". Object discarded.", image_path);
            this->z_order_set.erase(z_order);
            throw;
        }
        this->x = x;
        this->y = y;
        this->alpha = alpha;
        this->rot = rot;
        this->scale = scale;
    }
    /* Methods */
    SDL_Texture* get_texture(void) const {
        return this->texture;
    }
    std::pair<int64_t, int64_t> get_position(void) const {
        return {this->x, this->y};
    }
    void move_to(int64_t x, int64_t y) {
        this->x = x;
        this->y = y;
    }
    void move_from(int x, int y) {
        this->x += x;
        this->y += y;
    }
};

uint64_t _Object::count = 0;
std::set<int_fast64_t> _Object::z_order_set;

class _Button : public _Object {
    const char* image = path::img::menu_button; //the path is valid, i double-checked
public:
    _Button() = delete;
    _Button(SDL_Renderer* rend, int_fast64_t z_order, int x, int y)
        : _Object(rend, z_order, image, x, y) {}
    _Button(SDL_Renderer* rend, int_fast64_t z_order, int x, int y, uint8_t alpha, uint16_t rot, float scale)
        : _Object(rend, z_order, image, x, y, alpha, rot, scale) {}
    ~_Button() = default;
    bool was_clicked(int mouse_x, int mouse_y) {
        int txtrw, txtrh;
        SDL_QueryTexture(this->get_texture(), NULL, NULL, &txtrw, &txtrh);
        auto [xpos, ypos] = this->get_position();
        return (mouse_x >= xpos && mouse_x <= xpos + txtrw && mouse_y >= ypos && mouse_y <= ypos + txtrh);
    }
};

Проблема в том, что если я попытаюсь создать экземпляр _Object, он будет работать отлично:

_Object* my_object = new _Object(rend, 69, "whatever.png", 0, 0); //fine

Но когда я пытаюсь создать экземпляр _Button, программа вылетает:

_Button* my_button = new _Button(rend, 420, 0, 0); //crash

Я также заметил, что конструктор копирования тоже не работает:

_Object* my_object = new _Object(rend, 69, "whatever.png", 0, 0); //fine
_Object* my_object_2 = new _Object(rend, my_object); //crash

Я хотел бы понять, что может быть причиной этих проблем, потому что я понятия не имею. Возможно, я делаю что-то неправильно.

Я попытался попросить чатгпт о решении. Время потрачено впустую.

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

n. m. could be an AI 03.08.2024 08:16

Также старайтесь избегать создания/удаления, используйте std::make_unique. И const char* image_path; бесполезно std::string от этого. И действительно, прекратите использовать GOTO. Тот, кто учил вас C++, не очень хорошо справился со своей задачей. Поэтому, прежде чем продолжить (создавать игру), сделайте шаг назад и изучите еще немного основ C++ (например, Learncpp.com). А затем узнайте об абстрактных базовых классах, внедрении зависимостей и возможности модульного тестирования вашего кода (игры имеют тенденцию становиться большими и поэтому действительно нуждаются в предварительном проектировании).

Pepijn Kramer 03.08.2024 08:23

Обязательное предупреждение. Имена, начинающиеся с подчеркивания, за которым следует заглавная буква, зарезервированы для компилятора и не должны использоваться в пользовательском коде.

john 03.08.2024 08:36

Я не вижу причин для сбоя, но код ужасающий. Постарайтесь не полагаться на такое количество указателей.

john 03.08.2024 08:42

Вы можете сделать одну простую вещь: поскольку вызов конструктора копирования или оператора присваивания для вашего объекта не является хорошей идеей, вам следует удалить эти методы _Object(const _Object&) = delete; и _Object& operator=(const _Object&) = delete;. Если это вызывает какие-либо ошибки компилятора, значит, вы нашли ошибку.

john 03.08.2024 08:44
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
54
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Речь идет о порядке инициализации. Базовый класс инициализируется первым, а переменная пути производного класса — только позже. Это дает базовому классу неинициализированный указатель.

В стандарте написано:

В конструкторе без делегирования инициализация выполняется в следующий порядок:

— Во-первых, и только для конструктора наиболее производный класс (6.7.2), виртуальные базовые классы инициализируются в порядке, в котором они появляются при обходе слева направо в глубину. направленный ациклический граф базовых классов, где «слева направо» — порядок появления базовых классов в производном классе список-базовых спецификаторов.

— Тогда прямые базовые классы инициализируются в порядке объявления, как они появляются в список базовых спецификаторов (независимо от порядка инициализаторов памяти).

— Затем нестатические элементы данных инициализируются в порядке они были объявлены в определении класса (опять же независимо от порядок мем-инициализаторов).

— Наконец, выполняется составной оператор тела конструктора.

[Примечание 6: порядок декларации призван гарантировать, что база и член подобъекты уничтожаются в порядке, обратном инициализации. -конец примечание]

Предположительно, создание статического Button::image решило бы эту проблему.

john 03.08.2024 12:37

Я передал path::img::menu_button непосредственно конструктору, и это сработало.

class _Button : public _Object {
public:
    _Button(SDL_Renderer* rend, int_fast64_t z_order, int x, int y)
        : _Object(rend, z_order, path::img::menu_button, x, y) {}
/* it works! */

Другие вопросы по теме