构造函数退出时指针丢失引用
Pointers lose reference when constructor exits
下面是一个构造函数,用于形成统一的立方体网格 (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);
}
};
貌似cells
是在CellGrid
的构造函数中声明的,是吗?
std::vector<std::vector<std::vector<Cell>>> cells;
这是有问题的,因为 cells
按值包含所有 Cell
类。当 cells
存在作用域时(这发生在构造函数 returns 时,cells
变量被破坏,导致其向量的内容(和嵌套向量,因此 Cell
结构) 也被破坏。因此,指向 Cell
对象的指针是无效的,取消引用它们会导致未定义的行为。
相反,使 cells
成为 CellGrid
的私有成员变量,这样做将使 cells
的范围(以及单个 Cell
结构)保持活动状态,直到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);
}
}
}
我研究 your project 了一会儿才找到问题所在。尽管由于矢量更改导致指针失效,问题暴露出来了,但它只是删除了一步。该代码会导致问题,因为您要在 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()
公开维度的成员函数。
A CellGrid
可能不应该是默认构造的。
下面是一个构造函数,用于形成统一的立方体网格 (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);
}
};
貌似cells
是在CellGrid
的构造函数中声明的,是吗?
std::vector<std::vector<std::vector<Cell>>> cells;
这是有问题的,因为 cells
按值包含所有 Cell
类。当 cells
存在作用域时(这发生在构造函数 returns 时,cells
变量被破坏,导致其向量的内容(和嵌套向量,因此 Cell
结构) 也被破坏。因此,指向 Cell
对象的指针是无效的,取消引用它们会导致未定义的行为。
相反,使 cells
成为 CellGrid
的私有成员变量,这样做将使 cells
的范围(以及单个 Cell
结构)保持活动状态,直到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);
}
}
}
我研究 your project 了一会儿才找到问题所在。尽管由于矢量更改导致指针失效,问题暴露出来了,但它只是删除了一步。该代码会导致问题,因为您要在 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()
公开维度的成员函数。
A CellGrid
可能不应该是默认构造的。