使用矢量时的内存管理

时间:2017-07-29 23:39:47

标签: c++ vector memory-management

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

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

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

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

我知道其他容器没有这个问题,但我真的想让它适用于矢量。此外,值得注意的是,我事先并不知道将会有多少实体/组件。

非常感谢任何输入。

3 个答案:

答案 0 :(得分:5)

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

答案 1 :(得分:2)

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

我不觉得(差的措辞)是一个好主意 - >存储指向矢量中对象的指针。 (我的意思是创建指向向量元素的指针,即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调整大小时,&amp; my_vec [3]可能不再有效,因此client_ptr指向无处。

答案 2 :(得分:2)

  

如果向指针指向的向量添加了某些内容,则为   超出容量,我的理解是矢量会   分配新内存和指向任何元素的每个指针   向量将变为无效。

我曾写过一些代码来分析超出矢量容量时会发生什么。 (你做过这个吗?)这个代码在我的Ubuntu上用g ++ v5系统演示的是std :: vector代码简单地a)使容量加倍,b)将所有元素从旧存储移动到新存储,然后c)清理旧的。也许你的实现是类似的。我认为容量扩展的细节取决于实现。

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

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

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

2)我的做法是,我不会超过容量。是的,我的意思是我能够制定所有问题,以便了解必需最大容量。我从来没有发现这种方法是一个问题。

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

2017年7月31日更新

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

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

在我的实现中,每个Cell_t都有一个邻居'list'(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()可以返回一个表示“无邻居”的值(在顶部行的左上方,左上角等)

// 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数组包含索引整数,而不是指针。没有指针进入游戏板矢量是微不足道的。