当构造函数退出时,指针将失去引用

Pointers lose reference when constructor exits

本文关键字:失去 引用 指针 构造函数 退出      更新时间:2023-10-16

下面是一个构造函数,用于形成一个统一的立方体网格(cells),并将每个cell的邻居存储在一个向量中。

问题是,当我在构造函数的末尾放置断点时,neighbors向量中的所有引用都指向具有正确初始化值 ( b = 5) 的有效单元格。但是,当构造函数退出时,neighbors向量中的所有内容都变得未初始化,并指向我认为什么都没有(b = 负无穷大)。但是,cell本身(保存neighbors向量)仍然使用正确的值正确初始化。

当构造函数退出时,neighbors向量的大小也保持正确。由于某种原因,它似乎丢失了对单元格的引用。

CellGrid 头文件(为简洁起见,排除了方法)

class CellGrid {
public:
    CellGrid();
    CellGrid(int width, int height, int depth);
    ~CellGrid();
    int w;
    int h;
    int d;
    std::vector<std::vector<std::vector<Cell>>> cells;
};

CellGrid 构造函数部分从这里开始

for (int i = 0; i < w; i++) {
    for (int j = 0; j < h; j++) {
        for (int k = 0; k < d; k++) {
            cells[i][j][k] = Cell();
            cells[i][j][k].neighbors.reserve(27);
            cells[i][j][k].particles.reserve(8);
        }
    }
}
for (int i = 0; i < w; i++) {
    for (int j = 0; j < h; j++) {
        for (int k = 0; k < d; k++) {
            for (int x = -1; x < 2; x++) {
                for (int y = -1; y < 2; y++) {
                    for (int z = -1; z < 2; z++) {
                        if (i + x >= 0 && i + x < w && j + y >= 0 && j + y < h && k + z >= 0 && k + z < d) {
                            cells[i][j][k].addNeighbor(cells[i + x][j + y][k + z]);
                        }
                    }
                }
            }
        }
    }
}

细胞结构:

struct Cell {
    std::vector<Particle*> particles;
    std::vector<Cell*> neighbors;
    int b = 5;
    void addParticle(Particle &p) {
        particles.push_back(&p);
    }
    void addNeighbor(Cell &c) {
        neighbors.push_back(&c);
    }
};
我深入研究

了你的项目一段时间以找到问题所在。 尽管该问题暴露为指针因向量更改而失效,但它已被移除一步。 该代码会导致问题,因为您要在 ParticleSystem 构造函数的末尾复制整个CellGrid。 复制时,将使用所有单元格创建一个新向量,但它们的相邻指针引用从中复制的CellGrid。 违规代码:

ParticleSystem::ParticleSystem(float deltaT)   
{    
    this->deltaT = deltaT;    
    // ...a bunch of for loops...
    grid = CellGrid((int)width, (int)height, (int)depth);    
}

相反,您应该做的是在初始化列表中构造grid

ParticleSystem::ParticleSystem(float deltaT)
    : deltaT{deltaT},
    grid(width, height, depth)
{    
    this->deltaT = deltaT;    
    // ...a bunch of for loops...
}

此外,您应该从CellGrid中删除复制构造函数和赋值运算符,以便此问题不会在其他地方蔓延

class CellGrid {
  public:
    CellGrid();
    CellGrid(int width, int height, int depth);
    CellGrid(const CellGrid&) = delete;
    CellGrid& operator=(const CellGrid&) = delete;
   //...
};

这将防止问题首先出现。

另一个注意事项是,您的CellGrid值构造函数比调整向量大小所需的更复杂

CellGrid::CellGrid(int width, int height, int depth)
    : w{width},
    h{height},
    d{depth},
    cells(width, std::vector<std::vector<Cell>>(height, std::vector<Cell>(depth, Cell())))
{
    for (auto&& row : cells) {
        for (auto&& col : row ) {
            for (auto&& cell : col) {
                cell.neighbors.reserve(27);
                cell.particles.reserve(8);
            }
        }
    }
    for (int i = 0; i < w; i++) {
        for (int j = 0; j < h; j++) {
            for (int k = 0; k < d; k++) {
                for (int x = -1; x < 2; x++) {
                    for (int y = -1; y < 2; y++) {
                        for (int z = -1; z < 2; z++) {
                            if (i + x >= 0 && i + x < w && j + y >= 0 && j + y < h && k + z >= 0 && k + z < d) {
                                cells[i][j][k].addNeighbor(cells[i + x][j + y][k + z]);
                            }
                        }
                    }
                }
            }
        }
    }
}

由于宽度、高度和深度可以从向量维度推断出来,我还建议去掉变量并添加width()height()depth()成员函数来公开维度。

CellGrid可能不应该是默认可构造的。

除非在每个级别适当地调整cells大小,否则以下代码块会导致越界内存访问,并导致未定义的行为。

for (int i = 0; i < w; i++) {
    for (int j = 0; j < h; j++) {
        for (int k = 0; k < d; k++) {
            cells[i][j][k] = Cell();
            cells[i][j][k].neighbors.reserve(27);
            cells[i][j][k].particles.reserve(8);
        }
    }
}

您需要的是:

cells.resize(w);
for (int i = 0; i < w; i++) {
    cells[i].resize(h);
    for (int j = 0; j < h; j++) {
        cells[i][k].resize(d);
        for (int k = 0; k < d; k++) {
            // cells[i][j][k] = Cell(); <-- not needed; done by resize()
            cells[i][j][k].neighbors.reserve(27);
            cells[i][j][k].particles.reserve(8);
        }
    }
}

看起来cells是在CellGrid的构造函数中声明的,是这样吗?

std::vector<std::vector<std::vector<Cell>>> cells;

这是有问题的,因为cells按值保存所有Cell类。当cells存在作用域时(当构造函数返回时发生),cells变量被破坏,导致其向量(和嵌套向量,从而Cell结构)的内容也被破坏。因此,指向Cell对象的指针是无效的,取消引用它们会导致未定义的行为。

相反,使cells成为 CellGrid 的私有成员变量,这样做将使cells的作用域(以及单个结构Cell结构)保持活动状态,直到CellGrid本身被破坏。