Объявление объекта в стеке в определении класса и в конструкторе

Когда я объявляю объект «Уровень» в определении класса «LevelEditor» таким образом, все работает нормально:

class LevelEditor
{
public:
    LevelEditor(int w, int h, Shader* shader)
    {
        width = w;
        height = h;
        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                tile[x][y] = new WorldSprite(tileWidth * x, tileHeight * y, tileWidth, tileHeight, shader);
            }
        }
    }

    //...
private:

    //...
    Level level = Level(50, 50);

    WorldSprite* tile[300][300];

    //tile characteristics
    int tileWidth = 50;
    int tileHeight = 50;

    //flags
    bool editing = true;
};

Но когда я объявляю объект «Уровень» в конструкторе «LevelEditor» таким образом, я получаю переполнение стека:

class LevelEditor
{
public:
    LevelEditor(int w, int h, Shader* shader)
    {
        width = w;
        height = h;
        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                tile[x][y] = new WorldSprite(tileWidth * x, tileHeight * y, tileWidth, tileHeight, shader);
            }
        }
        //NOTE: width and height both equal 50
        level = Level(width, height);
    }

    //...
private:

    //...
    Level level;

    WorldSprite* tile[300][300];

    //tile characteristics
    int tileWidth = 50;
    int tileHeight = 50;

    //flags
    bool editing = true;
};

Это заставляет меня задаться вопросом, в чем разница между объявлением переменной в определении класса и в конструкторе, помимо факта времени определения переменной. Любая идея о том, что может быть причиной? и как я могу объявить объект «Уровень» в конструкторе, не помещая ничего в кучу?

РЕДАКТИРОВАТЬ: определение класса «Уровень», если это полезно:

class Level
{
public:
    Level(int w, int h)
    {
        Worldwidth = w;
        Worldheight = h;

        for (unsigned int y = 0; y < Worldheight; y++)
        {
            for (unsigned int x = 0; x < Worldwidth; x++)
            {
                grid[x][y] = -1;
            }
        }
    }
    Level(){}
    ~Level()
    {
        for (auto it = tiles.begin(); it != tiles.end(); ++it)
        {
            delete *it;
        }
        tiles.clear();

        for (auto it = entities.begin(); it != entities.end(); ++it)
        {
            delete *it;
        }
        entities.clear();
    }

    void draw()
    {

    }
private:

    int Worldwidth;
    int Worldheight;

    int grid[300][300];

    std::vector<Tile*> tiles;
    std::vector<Entity*> entities;
};

person Charles Hetterich    schedule 20.03.2015    source источник
comment
Имеет ли класс Level правильный конструктор копирования? Покажите нам определение вашего класса Level.   -  person Ron Tang    schedule 20.03.2015
comment
ideone.com/inKGMR Это как минимум количество байтов, которое занимает один из ваших LevelEditor объектов. Пересмотрите свой дизайн. Вы объявляете отдельные объекты размером более 300 КБ.   -  person PaulMcKenzie    schedule 20.03.2015
comment
@RonTang не является конструктором копирования, что С++ автоматически дает классы без необходимости определять его вручную?   -  person Charles Hetterich    schedule 20.03.2015
comment
@CharlesHetterich Вы получаете переполнение стека. Это указывает на то, что ваши объекты, возможно, слишком велики для копирования, и вам не хватает места в стеке. Вместо массивов используйте вектор и измените его размер в конструкторе.   -  person PaulMcKenzie    schedule 20.03.2015
comment
@CharlesHetterich Кроме того, ваш класс Level не будет безопасно скопирован из-за того, что ваш класс использует указатели (вектор указателей) в качестве членов. Для этого требуется пользовательский конструктор копирования и оператор присваивания, так как значения по умолчанию не подходят. Ваш деструктор для Level удаляет все выделенные экземпляры в векторе, поэтому level = Level(...) оставляет level с фиктивными значениями. Я предлагаю вам использовать векторы и, возможно, std::shared_ptr вместо необработанных указателей.   -  person PaulMcKenzie    schedule 20.03.2015
comment
@PaulMcKenzie Я только что закомментировал массив 300x300, и уровень, определенный в конструкторе, сработал. Но почему указатель влияет на переполнение стека, если указатель находится в куче?   -  person Charles Hetterich    schedule 20.03.2015
comment
@CharlesHetterich Посмотрите на код, на который я ссылаюсь в своем комментарии. Видите, сколько байтов занимает это определение? Это array, которого нет в куче, и именно массив занимает все это пространство.   -  person PaulMcKenzie    schedule 20.03.2015
comment
@CharlesHetterich PaulMcKenzie прав.   -  person Ron Tang    schedule 20.03.2015


Ответы (1)


Есть несколько проблем с вашим кодом. Я постараюсь исправить ошибку stack overflow. Другая проблема заключается в том, что ваш класс Level нельзя безопасно копировать — об этом можно позаботиться, используя интеллектуальные указатели, такие как std::unique_ptr и std::shared_ptr.

Во-первых, ваши классы используют массивы 300 x 300 T, в одном случае T — это WorldSprite*, а в другом — int. Массивы такого размера, объявленные как элементы, увеличат размер каждого из ваших объектов, содержащих их, до сотен килобайт. В какой-то момент это скажется на стеке.

Поэтому вам следует удалить эти определения и вместо них использовать std::vector.

#include <vector>

class LevelEditor
{
public:
    LevelEditor(int w, int h, Shader* shader) : 
                tile(w,std::vector<WorldSprite*>(h))
                editing(true), width(w), height(h)
    {
        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
                tile[x][y] = new WorldSprite(tileWidth * x, tileHeight * y, 
                                             tileWidth, tileHeight, shader);
        }
        level = Level(width, height);
    }
private:
    Level level;
    int width, height;
    std::vector<std::vector<WorldSprite*>> tile;
    bool editing;
};

Вот класс Level с такими же изменениями:

#include <vector>
//...
class Level
{
public:
    Level(int w, int h) : Worldwidth(w), Worldheight(h), 
                          grid(300, std::vector<int>(300, -1))
    {}

    Level(){}

    ~Level()
    {
        for (auto it = tiles.begin(); it != tiles.end(); ++it)
        {
            delete *it;
        }
        tiles.clear();

        for (auto it = entities.begin(); it != entities.end(); ++it)
        {
            delete *it;
        }
        entities.clear();
    }

    void draw()
    {
    }
private:
    int Worldwidth;
    int Worldheight;
    std::vector<std::vector<int> >grid;
    std::vector<Tile*> tiles;
    std::vector<Entity*> entities;
};

Обратите внимание, что вектор заменяет массив и для инициализации будет использовать динамическую память. В классе Level мы инициализируем вектор и устанавливаем все записи в -1 одним вызовом конструктора вектора.

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


Другая проблема заключается в том, что ваш класс Level не является безопасным для копирования (как и LevelEditor, но я оставлю его в покое, поскольку можно внести тот же набор изменений).

Проблема будет в этой строке:

level = Level(width, height);

Проблема с этой строкой в ​​том, что будет вызван оператор присваивания и может быть вызван конструктор копирования. Если вы посмотрите на свой класс Level, у него есть деструктор, который удаляет все указатели из векторов, содержащих указатели. Это будет иметь катастрофические последствия, если вы скопируете Level объектов, поскольку вы уничтожите все свои данные из-за уничтожения временных файлов.

Если нет никакого смысла в том, какой Level на самом деле владеет указателями, и все сводится к тому, что «кто бы ни был последним человеком, стоящим, является владельцем», и вы фактически будете совместно использовать указатели между экземплярами Level (вот почему он называется shared_ptr), то вы можете использовать это решение:

#include <vector>
#include <memory>
//...
class Level
{
public:
    Level(int w, int h) : Worldwidth(w), Worldheight(h), 
                          grid(300, std::vector<int>(300, -1))
    {}

    Level(){}
    void draw()
    {
    }
private:
    int Worldwidth;
    int Worldheight;
    std::vector<std::vector<int>> grid;
    std::vector<std::shared_ptr<Tile>> tiles;
    std::vector<std::shared_ptr<Entity>> entities;
};

Обратите внимание, что здесь нет кода деструктора — его и не должно быть. Удаление выполняется shared_ptr, так что вам нечего делать — все управляется. Что произойдет, так это то, что последнее Level, которое будет уничтожено, с которым вы поделились указателями, выполнит фактическое удаление. Итак, когда эта линия сделана

level = Level(width, height);

копирование объектов Level увеличивает и уменьшает внутренний счетчик ссылок shared_ptr, оставляя счетчик ссылок равным 1 (это последний level слева от знака =).

См. здесь использование std::shared_ptr: http://en.cppreference.com/w/cpp/memory/shared_ptr

Обратите внимание, что вы можете использовать std::unique_ptr, если право собственности является проблемой. Я предлагаю вам искать SO для использования std::unique_ptr. Я показал вам std::shared_ptr, так как он самый простой на данный момент (но опять же, может не соответствовать всем вашим потребностям - YMMV).

person PaulMcKenzie    schedule 20.03.2015