Утечки памяти в указателе вектора - PullRequest
0 голосов
/ 15 ноября 2018

Я создаю программу, которая использует множество векторов, содержащих указатели, и я обеспокоен тем, что способ удаления указателей может привести к утечке памяти.

Вот, например, как добавить маркер к вектору:

Bullet* aNewBullet;
aNewBullet = new Bullet(x,y,dirX,dirY,size,duration);
aNewBullet->assignTexture(btext);
bulletVector.push_back(aNewBullet);

А вот как я удаляю маркеры из вектора (например, если он превышает его диапазон)

int i=0;
while (i<bulletVector.size()){
    if (bulletVector[i]->getDuration() ==0)
        bulletVector.erase(bulletVector.begin() + i);
    else i++;
}

Я удаляю пулю, приказывая стереть вектор? Или я должен сделать

int i=0;
while (i<bulletVector.size()){
    if (bulletVector[i]->getDuration() ==0)
        delete bulletVector[i];
        bulletVector.erase(bulletVector.begin() + i);
    else i++;
}

вместо этого? Это портит размер вектора, удаляя сначала указатель, а затем снова удаляя запись из вектора?

Также вызывает ли удаление указателя деструктор пули (~ Bullet ())?

Ответы [ 4 ]

0 голосов
/ 15 ноября 2018

Во-первых, это очень , скорее всего, ваша проблема разрешима, если в первую очередь не использовать динамическое распределение вручную. Есть причины, по которым вы можете использовать динамическое выделение (например, полиморфное хранилище), но даже в этом случае вам следует ориентироваться на интеллектуальные указатели, а не на выделение вручную.

Отключить указатели

Чтобы сделать это с регулярным вектором конкретных объектов (не указателей), вы просто

std::vector<Bullet> bulletVector;

Дополнения могут быть сделаны примерно так:

bulletVector.emplace_back(x,y,dirX,dirY,size,duration);
bulletVector.back().assignTexture(btext);

Удаление всех элементов на основе длительности, достигающей нуля, будет просто использовать удалить / стереть идиома, как это:

bulletVector.erase(std::remove_if(
                    bulletVector.begin(), 
                    bulletVector.end(),
                    [](auto const& b) { return b.getDuration() == 0; }),
                   bulletVector.end());

Имейте в виду, что хотя этот метод хранения данных и является наиболее предпочтительным, он потребует изменений в остальной части кода. Везде, где вы сейчас делаете это:

bulletVector[i]->doSomething();

станет

bulletVector[i].doSomething();

Больше интеллектуальных указателей

Если вам нужно использовать динамическое размещение, не используйте указатели, управляемые вручную . Это рецепт утечки памяти и ответственности за владение, и если вы думаете, что с вами этого не случится, вы наивны. Скорее используйте умные указатели. Два основных шаблона интеллектуальных указателей: std::unique_ptr и std::shared_ptr. Также есть std::weak_ptr, которые заслуживают похвального упоминания, поскольку в некоторых ситуациях это очень удобно (не ваша, как я могу сказать).

std::vector<std::shared_ptr<Bullet>> bulletVector;

Следовательно, дополнения могут быть выполнены следующим образом (при условии, что это сделано с учетом полиморфизма и может быть более одного типа пули, в данном случае MagicBullet и SilverBullet, оба получены из Bullet):

std::shared_ptr<Bullet> bullet = std::make_shared<MagicBullet>(x,y,dirX,dirY,size,duration);
bullet->assignTexture(btext);
bulletVector.emplace_back(bullet);

bullet = std::make_shared<SilverBullet>(x,y,dirX,dirY,size,duration);
bullet->assignTexture(btext);
bulletVector.emplace_back(bullet);

или просто храните обычные патроны:

bullet = std::make_shared<Bullet>(x,y,dirX,dirY,size,duration);
bullet->assignTexture(btext);
bulletVector.emplace_back(bullet);

Алгоритм удаления идентичен, только функция предиката условия изменяется (и ненамного)

bulletVector.erase(std::remove_if(
                    bulletVector.begin(), 
                    bulletVector.end(),
                    [](auto const& b) { return b->getDuration() == 0; }),
                   bulletVector.end());

Самая приятная часть этого кода - оставшаяся часть вашего кода, скорее всего, практически не потребует никаких изменений. Но я подчеркиваю, это реальная выгода only (за исключением очевидного, а именно, больше не нужно управлять памятью самостоятельно).


Учитывая выбор между двумя методами (вектор конкретных объектов против вектора умных указателей), выбирайте осторожно. Если первое работает, используйте его. Если это не так, либо подумайте о том, как он может работать, либо воспользуйтесь последним.

Но прежде всего прекратить управление памятью вручную .

0 голосов
/ 15 ноября 2018

В первом примере кода вы не удаляете ни один объект Bullet. Стирание vector только удаляет указатели, но не удаляет указанный объект.

Ваш второй пример правильно удаляет Bullet s (не все из них с учетом условий и настройки цикла), но только если вы делаете необходимое удаление во всех точках, где вектор изменен, и вручную гарантируете, что указатель никогда не будет удаляется дважды (например, потому что он присутствует в двух векторах). Это не то, как контейнеры должны использоваться.

Если вы действительно хотите хранить указатели и иметь собственный вектор, тогда вам следует использовать std::vector<std::unique_ptr<Bullet>>, который будет правильно удалять объекты Bullet при удалении вектора. Однако вектор станет не копируемым (только подвижным).

Если, однако, в любом случае предполагается, что вектор является владельцем Bullet, вы можете просто сохранить их непосредственно в векторе: std::vector<Bullet>

Если вектор не должен владеть Bullet s, тогда обязанность удалять их лежит на том объекте, которым он на самом деле принадлежит, но даже в этом случае необходимо приложить некоторые усилия, чтобы гарантировать, что удаленные указатели не останутся в вектор.

0 голосов
/ 15 ноября 2018

Вам необходимо delete объекты, которые вы создали с помощью new (если вы не сдадите указатель на std::unique_ptr или std::shared_ptr).Вместо этого, пусть вектор будет владельцем объектов.

std::vector<Bullet> bulletVector;

// in C++17, emplace_back returns a ref to the new Bullet:
Bullet& aNewBullet = bulletVector.emplace_back(x,y,dirX,dirY,size,duration);

// pre C++17:
bulletVector.emplace_back(x,y,dirX,dirY,size,duration);
Bullet& aNewBullet = bulletVector.back();

aNewBullet.assignTexture(btext);

Когда вы теперь erase() объект из вектора, он будет автоматически уничтожен.

0 голосов
/ 15 ноября 2018

Конечно, вы должны удалить Bullets самостоятельно, удалив. Но ваш цикл удаления неправильный.

Чтобы удалить значения из вектора с некоторым условием, используйте что-то вроде этого:

auto condFunc = [](const Bullet* x)
{
    bool rez = (x->getDuration() == 0);
    if(rez)
        delete x;
    return rez;
};
bulletVector.erase(std::remove_if(bulletVector.begin(), bulletVector.end(), condFunc), bulletVector.end());
...