在 class 定义中与在构造函数中将对象声明到堆栈上

Declaring object onto the stack in class definition vs in constructor

当我在 "LevelEditor" class 定义中声明 "Level" 对象时,一切正常:

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" 构造函数中声明 "Level" 对象时,出现堆栈溢出:

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" 对象而不必在堆上放置任何东西?

编辑: "Level" class 定义以防有帮助:

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;
};

您的代码存在几个问题。我将尝试解决 stack overflow 错误。另一个问题是您的 Level class 不可安全复制——可以通过使用 std::unique_ptrstd::shared_ptr.[=44= 等智能指针来解决。 ]

首先,您的 classes 使用 300 x 300 个 T 数组,在一种情况下,TWorldSprite*,另一个是 int .声明为成员的这种大小的数组将使包含它们的每个对象的大小膨胀到数百 KB。这在某些时候会对堆栈造成影响。

因此您应该删除这些定义,而改用 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 class 具有相同类型的变化:

#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;
};

注意vector代替array,会使用堆内存来初始化。在 Level class 中,我们初始化向量并将所有条目设置为 -1,一次调用向量的构造函数。

这不会将对象的大小增加到非常高的原因是 vector 将在堆上创建其数据(除非您有某种自定义分配器从另一个来源获取内存)。因此,您的 classes 的大小将是合理的(可能小于 100 字节)。


另一个问题是您的 Level class 无法安全复制(LevelEditor 也不是,但我不会管它,因为同一组更改可以完成)。

问题出在这一行:

level = Level(width, height);

这一行的问题是会调用赋值运算符,可能会调用拷贝构造函数。如果您查看 Level class,它有一个析构函数,可以从包含指针的向量中删除所有指针。如果您复制 Level 个对象,这将是灾难性的,因为由于临时对象被销毁,您将销毁所有数据。

如果不知道哪个Level实际上拥有这些指针,归结为"whoever is the last man standing is the owner",你实际上会共享个指针在 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)。