我正在制作一个游戏引擎,需要将std :: vector容器用于游戏中的所有组件和实体。
在脚本中,用户可能需要持有指向实体或组件的指针,可能需要持续检查某种状态。如果向指针指向的向量添加了某些内容并且超出了容量,则我理解向量将分配新内存,并且指向向量中任何元素的每个指针都将变为无效。
考虑到这个问题,我有几个可能的解决方案。在每次push_back到向量之后,检查向量的实际容量是否超过当前容量变量是否可行?如果是这样,获取并覆盖旧指针的旧指针?这可以保证“捕获”执行push_back时使指针无效的每个案例吗?
我发现的另一个解决方案是将索引保存到元素并以这种方式访问它,但我怀疑当你需要不断检查该元素的状态时(每1/60秒)这对性能有害)。
我知道其他容器没有这个问题,但我真的想让它适用于矢量。此外,值得注意的是,我事先并不知道将会有多少实体/组件。
非常感谢任何输入。
答案 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数组包含索引整数,而不是指针。没有指针进入游戏板矢量是微不足道的。