Я новичок в C++, и сейчас я работаю над игрой. В игре у меня будет случайное вражеское поколение с течением времени, и я использую std :: vector, чтобы сделать это. Я думал, что могу использовать что-то вроде
enemies.push_back(new Enemy(random_x, randomY));
в пределах цикла, но теперь мне кажется, что это может привести к утечке памяти, поскольку я не удаляю указатели. Поэтому я хотел бы узнать, что я должен сделать, чтобы предотвратить утечку памяти. должен ли я найти способ удалить всех врагов, которых я создаю, или должен ли я сделать что-то совершенно другое?
Существуют различные способы решения этой проблемы.
Вы можете использовать необработанные указатели, как в std::vector<Enemy*>
, тогда вам нужно удалить их при их удалении:
delete *pos; // pos is an iterator pointing to the element to be deleted
enemies.erase(pos);
Однако это не способ обрабатывать вещи в C++.
Вы можете использовать shared_ptr
если хотите передать "врагов", не имея доступа к списку, как в std::vector<std::shared_ptr<Enemy>>
. Затем вам просто нужно стереть, и память будет автоматически освобождена для вас, как только никакая shared_ptr
указывающая на врага, больше не будет существовать.
enemies.erase(pos); // Nothing more needed, if someone still has a shared_ptr-copy
// of the enemy it will stay in memory until that reference is gone
Если вы всегда обращаетесь к врагу с помощью списка, вы можете даже использовать std::unique_ptr
. Но в этом случае может быть даже лучше просто перейти с простейшей из всех форм: std::vector<Enemy>
. Таким образом, вам никогда не придется называть new
. Но это также мешает вам использовать полиморфизм, и копии могут быть очень дорогими в зависимости от вашего Enemy
-class.
std::vector<Enemy>
хранит полный Enemy
в нем, копируя его. Если, например, Enemy
занимает 8 байтов в памяти, и у вас есть 5 врагов в векторе, теоретически он займет 40 байт памяти (может потребоваться больше, но это специфично для реализации)
| Idx0 | Idx1 | Idx2 | Idx3 | Idx4 |
[Enemy1][Enemy2][Enemy3][Enemy4][Enemy5]
8b 8b 8b 8b 8b
Если вы попытаетесь сохранить производный объект ExtraToughEnemy
с добавленными атрибутами в позиции 3, который требует, скажем, 4 байта, он не будет работать, потому что каждая запись в векторе должна иметь тот же размер.
| Idx0 | Idx1 | Idx2 | Idx3 | Idx4 |
[Enemy1][Enemy2][ExtraToughEnemy3][Enemy4][Enemy5]
8b 8b 12b 8b 8b
Если вы используете указатели, вы избегаете этой проблемы, так как каждый объект-указатель имеет одинаковый размер, но может указывать на разные производные объекты.
| Idx0 | Idx1 | Idx2 | Idx3 | Idx4 |
[Enemy1*][Enemy2*][Enemy3*][Enemy4*][Enemy5*]
4b 4b 4b 4b 4b
Указатель базового слова может указывать на любой базовый класс или производный объект. Вы должны будете использовать бросок для доступа к дополнительным атрибутам Enemy3*
.
Если вы их используете, вы можете рассмотреть (в зависимости от вашего компилятора), даже пропустить с помощью new
вообще и использовать std::make_shared
или std::make_unique
для создания объектов в куче.
std::vector<Enemy>
может удержать меня от полиморфизма. Вы можете это объяснить?
Там только утечка памяти, если вы потеряете доступ к объекту, не освободив сначала хранилище резервных копий. Это не так, потому что вы призываете найти всех этих врагов, просто пройдя через коллекцию enemies
.
Предположительно, в какой-то момент вы удалите врагов из списка (например, когда вы убиваете их в игре). В этот момент вы должны удалить их, и память будет восстановлена.
Enemy enemy1;
было бы просто удалить их, просто вызовите delete enemy1
. Но так как это несовместимо с динамическим списком и большим количеством врагов, которые я создаю, мне было интересно, смогу ли я что-то сделать, например, delete enemies.at(i)
в цикле, когда я закончу с ними.
enemies
.