使用矢量时的内存管理

Memory management when using vector

本文关键字:内存 管理      更新时间:2023-10-16

我正在制作一个游戏引擎,需要对游戏中的所有组件和实体使用 std::vector 容器。

在脚本中,用户可能需要持有指向实体或组件的指针,可能需要持续检查某种状态。如果向指针指向的向量中添加了某些内容并且超出了容量,则我的理解是,向量将分配新的内存,并且指向向量中任何元素的每个指针都将变得无效。

考虑到这个问题,我有几个可能的解决方案。每次push_back矢量后,检查当前容量变量是否超过矢量的实际容量是否可行?如果是这样,获取并覆盖指向新指针的旧指针?这是否保证在执行push_back时"捕获"每个使指针无效的情况?

我发现的另一种解决方案是将索引保存到元素并以这种方式访问它,但是当您需要持续检查该元素的状态(每 1/60 秒)时,我怀疑这对性能不利。

我知道其他容器没有这个问题,但我真的很想让它与矢量一起工作。另外值得注意的是,我事先不知道会有多少实体/组件。

任何意见都非常感谢。

当你每秒只访问它的元素 60 次时,你不应该担心 std::vector 的性能。顺便说一下,在发布编译模式下,std::vector::operator[]正在转换为单个lea操作码。在调试模式下,它由一些运行时范围检查装饰。

如果用户要存储指向对象的指针,为什么还要将它们包含在向量中?

我觉得(措辞不佳)>存储指向向量中的对象的指针不是一个好主意。(我的意思是创建指向向量元素的指针,即 my_ptr = &my_vec[n];) 容器的全部意义在于以容器支持的正常方式引用内容,而不是创建指向容器元素的外部指针。

要回答您关于是否可以检测到分配的问题,是的,您可以,但是通过指向元素的指针来引用向量的内容仍然可能是一个坏主意。

您也可以在创建矢量时保留矢量中的空间,如果您知道最大大小可能增长到什么程度。 然后它永远不会调整大小。

编辑:

在阅读了其他回复并思考了您的问题之后,另一个想法发生了。 如果向量是指向对象的指针向量,并且您将指向对象的指针传递给客户端,则调整矢量大小不会使矢量包含的指针无效。 问题变成了跟踪对象(谁拥有它)的生命周期,这就是为什么使用 shared_ptr 会很有用的原因。

例如:

vector<shared_ptr> my_vec;
my_vec.push_back(stuff);

如果您将向量中包含的指针传递给客户端...

client_ptr = my_vec[3];

当矢量调整大小时不会有问题。 向量的内容将被保留,my_vec[3]处的内容仍将存在。 my_vec[3] 指向的对象仍将位于同一地址,并且 my_vec[3] 仍将包含该地址。 无论谁在 my_vec[3] 处获得了指针的副本,都将仍然具有有效的指针。

但是,如果您这样做:

client_ptr = &my_vec[3];

客户端像这样取消引用:

*client_ptr->whatever();

你有问题。 现在,当my_vec调整大小时,&my_vec[3] 可能不再有效,因此client_ptr无处可去。

如果将某些内容添加到指针指向的向量中,并且 超出容量,我的理解是矢量会 分配新内存和指向 矢量将变为无效。

我曾经写过一些代码来分析当超过向量的容量时会发生什么。 (你这样做了吗?该代码在我的 Ubuntu 上使用 g++v5 系统演示的是 std::vector 代码只是 a) 将容量翻倍,b) 将所有元素从旧存储移动到新存储,然后 c) 清理旧存储。 也许您的实现是相似的。 我认为产能扩张的细节取决于实施。


是的,当 push_back() 导致超出容量时,任何进入矢量的指针都将失效。

1)我根本不使用指向向量的指针(你也不应该)。 通过这种方式,问题完全消除了,因为它根本不可能发生。 (另请参阅悬空指针) 访问 std::vector(或 std::array)元素的正确方法是使用索引(通过运算符 []() 方法)。

在任何容量扩展之后,索引小于先前容量限制的所有元素的索引仍然有效,因为 push_back() 在"末尾"安装了新元素(我认为解决了最高内存问题)。 元素内存位置可能已更改,但元素索引仍然相同。

2)我的做法是我只是不要超过能力。 是的,我的意思是我已经能够提出我的所有问题,以便我知道所需的最大容量。 我从来没有发现这种方法是一个问题。

3)如果矢量内容不能包含在系统内存中(我的系统的最佳上限容量大约是3.5 GB),那么矢量容器(或任何基于ram的容器)可能是不合适的。 您必须使用磁盘存储来实现目标,也许使用矢量容器充当缓存。


更新 2017-7月-31

从我最新的《生命游戏》中可以考虑的一些代码。

每个Cell_t(在 2-D 游戏板上)有 8 个邻居。

在我的实现中,每个Cell_t都有一个邻居"列表"(std::array 或 std::vector,我已经尝试了两者),并且在游戏板完全构造后,每个Cell_t的 init() 方法都会运行,填充它的邻居"列表"。

// see Cell_t data attributes
std::array<int, 8> m_neighbors;
// ...
void Cell_t::void init()
{
int i = 0;
m_neighbors[i]   = validCellIndx(m_row-1, m_col-1);  // 1 - up left
m_neighbors[++i] = validCellIndx(m_row-1, m_col);    // 2 - up
m_neighbors[++i] = validCellIndx(m_row-1, m_col+1);  // 3 - up right
m_neighbors[++i] = validCellIndx(m_row,   m_col+1);  // 4 - right
m_neighbors[++i] = validCellIndx(m_row+1, m_col+1);  // 5 - down right
m_neighbors[++i] = validCellIndx(m_row+1, m_col);    // 6 - down
m_neighbors[++i] = validCellIndx(m_row+1, m_col-1);  // 7 - down left
m_neighbors[++i] = validCellIndx(m_row,   m_col-1);  // 8 - left
//                 ^^^^^^^^^^^^^- returns info to quickly find cell
}

m_neighbors[i] 中的 int 值是游戏板向量的索引。 为了确定单元格的下一个状态,代码"计算邻居的状态"。

注意 - 某些单元格位于游戏板的边缘...在此实现中,validCellIndx() 可以返回一个指示"no-neighbor"的值(顶行上方、左边缘左侧等)。

// multiplier: for 100x200 cells,20,000 * m_generation => ~20,000,000 ops
void countNeighbors(int& aliveNeighbors, int& totalNeighbors)
{
{ /* ... initialize m_count[]s to 0 */ }
for(auto neighborIndx : m_neighbors ) { // each of 8 neighbors  // 123
if(no_neighbor != neighborIndx)                              // 8-4
m_count[ gBoard[neighborIndx].m_state ] += 1;             // 765
} 
aliveNeighbors = m_count[ CellALIVE ]; // CellDEAD = 1, CellALIVE
totalNeighbors = aliveNeighbors + m_count [ CellDEAD ];
} // Cell_Arr_t::countNeighbors

init() 预先计算此单元格邻居的索引。m_neighbors数组保存索引整数,而不是指针。 没有指向游戏板矢量的指针是微不足道的。