Note: this article was written in April 2022.
I was working on a project recently and ran into an issue with memory that took me a few days to figure out. This is the sort of code that I’d written:
#include <iostream>
#include <vector>
struct SceneObject{
std::vector<int> entities;
} aScene;
void createEntity() {
aScene.entities.push_back(5);
}
int main() {
createEntity();
int* heldPointer = aScene.entities[0];
std::cout << "held pointer: " << heldPointer << "\t m_id" << *heldPointer << "\n";
std::cout << "good pointer: " << &aScene.entities[0] << "\n";
createEntity();
std::cout << "held pointer: " << heldPointer <<"\t m_id" << *heldPointer << "\n";
std::cout << "good pointer: " << &aScene.entities[0] << "\n";
return 0;
}
This is a simplified example of the actual code. The actual code had a vector of entities and a std::map
of entity ids to entity pointers. I hadn’t used std::map
before so for a long time I assumed the bug was in how I was using std::map
. I went through a long series of different possible explanations for why things were happening.
The behavior was pretty odd, the entities were correct when inserted. Then the .back()
of the std::map
was always correctly pointing to the newest entity. In fact, when I had 100 elements in the std::map
the last 6 or so were actually correct. This was pretty baffling, if something was causing the data to be erased, why would it only erase 94% of the data?
I thought the issue was that I was misunderstanding the object lifetime, I thought, well I was instantiating the object inside a function so maybe it is deleted when I leave the function. Or it deletes it the next time the function is called (that explanation stood for awhile because that’s exactly the behavior that was showing up, calling createEntity()
was what was deleting the data, it explained what was happening pretty well).
The actual issue was that std::vector
grows as you add elements to it, and each time it grows it allocates new memory and moves all the old data into the new memory. You can see this in the example code above, because &aScene.entities[0]
changes as elements are added (depending on your compiler you might have to call createEntity()
a few times). This means that pointers to the old data will be invalidated when the vector grows. So the solution here for my simple project would be to just use a non-dynamic array, just allocate 100 elements in the first place and then pointers to the data won’t have to be updated… Or have a system for updating the pointers each time the array grows.