6

Like the codes below, m_vSprites is a vector of shred_ptr, if one of his elements update fail, I would like to erase it from the vector, but my codes crash when I would like to using erase. But I don't why, anyone can help?

The reason I need to use erase is because my application would continually add elements in the vector, but would also continually erase objects from vector if some elements meets their killing conditions. if I didn't erase it, the vector would become huge as the program works!

RECT rcOldSpritePos;
    typedef boost::shared_ptr<Sprite> SmartSprite;
vector<SmartSprite>::iterator siSprite;
for (siSprite = m_vSprites.begin(); siSprite != m_vSprites.end();)
{
    // Save the old sprite position in case we need to restore it
    rcOldSpritePos = (*siSprite)->getPosition();

    if (!((*siSprite)->update()))
    {
        // Kill the sprite
        //siPreviousSprite = siSprite-1;
        siSprite = m_vSprites.erase(siSprite);
    }
            else
            {
                  siSprite++;
                  // See if the sprite collided with any others
          if (checkSpriteCollision(*siSprite))
        // Restore the old sprite position
        (*siSprite)->setPosition(rcOldSpritePos.left, rcOldSpritePos.top);
            }

}

I have changed the codes as someone suggested, but still failed in the erase function, Please anyone have some suggestions?

Some more information how I add an element in the vecotr

Any problem here?

    SmartSprite sprite;
if (0 < enemies.size())
{
    // Pick a random enemy to drop bomb
    size_t nRandEnemy = (rand() % enemies.size());
    RECT rRandEnemy = enemies.at(nRandEnemy)->getPosition();
    sprite.reset(new BombSprite(m_system, rRandEnemy.right-OBJECTSIZE/2,   
            rRandEnemy.bottom));
    m_vSprites.push_back(sprite);
}

My sprite class didn't have any destructor...

one more information is, while I debug, found it crash in erase internal function: _Destroy(_Mylast - 1, _Mylast);


problem solved!, the reason is in my Sprite class, I wrap it as a smart pointer, and created another smart pointer as its member variable. now I didn't use the smart pointer member variable, and the system didn't crash again.... I will continue to think about why I can't use a smart pointer inside that class, when erase sprite from vector, does it matter in his member variable? do I still need to delete that member variable if only use raw pointer instead of suing smart pointer?

Pengzhi Zhou
  • 481
  • 1
  • 6
  • 24
  • My guess is that your sprite destructor is where the error is. What you're doing here is perfectly fine, well, except for the moving backwards on the 0th element bit. – Omnifarious Nov 19 '12 at 22:50
  • 1
    After the edit your code is still incorrect. Compare with the code in the answers below, you are incrementing your iterator wrongly when you erase. – john Nov 19 '12 at 22:53
  • Take the `++siSprite` out of the header of the for loop (leave the increment operation blank), and put it at the bottom of the loop's body. What you're going for is to only increment the iterator on iterations where you didn't erase. On iterations where you do erase, the iterator's position is set correctly by catching the return value of the erase function. – Benjamin Lindley Nov 19 '12 at 22:56

4 Answers4

6

If the iterator you are about to erase is the vector's begin iterator, then this;

siPreviousSprite = siSprite - 1;

Is undefined behavior. Restructure your loop like this:

for (siSprite = m_vSprites.begin(); siSprite != m_vSprites.end(); )
{

    rcOldSpritePos = (*siSprite)->getPosition();

    if (!((*siSprite)->update()))
    {
        // Kill the sprite
        siSprite = m_vSprites.erase(siSprite);            
    }
    else
    {
        ++siSprite;
    }
}

If there is still a crash, then there must be something wrong with your sprite class.

Benjamin Lindley
  • 101,917
  • 9
  • 204
  • 274
  • Thanks for the suggestion, I have changed the codes like you suggested, but still crash, actually the one crash is generally not the first element in the loop, it just anyone which update failed then come in the erase section. – Pengzhi Zhou Nov 19 '12 at 22:46
  • @PengzhiZhou: Well, there's nothing wrong with removing a shared_ptr from a vector like this, so the problem must be with your sprite class. We'll need to see it. The most likely candidate, I would guess, is a double delete. Does your class obey the [Rule of Three](http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three)? – Benjamin Lindley Nov 19 '12 at 22:48
  • As you said, there may a bug in my Sprite class code, but indeed there is not desctructor for sprite. after debug, I found it exactly fail in the internal function "_Destroy(_Mylast - 1, _Mylast);" of erase. I am still looking at the codes.~ thanks for your help – Pengzhi Zhou Nov 19 '12 at 23:23
  • @PengzhiZhou: Maybe you have a problem in your `checkSpriteCollision()`? – Mr.C64 Nov 19 '12 at 23:28
  • @PengzhiZhou: Did you fix your loop as I suggested in the comments below your question? – Benjamin Lindley Nov 19 '12 at 23:30
  • @Mr.C64, no ~ it is crashed directly in m_vSprites.erase function, I have debug it. – Pengzhi Zhou Nov 19 '12 at 23:30
  • @Benjamin Lindley, yes, but it doesn't work, the application crashed and can't even start. – Pengzhi Zhou Nov 19 '12 at 23:34
  • @PengzhiZhou: I'd say that @BenjaminLindley's code is quite idiomatic. If you have a crash it's probably caused by code you haven't showed. BTW: Did you try using the [*erase-remove* idiom](http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Erase-Remove)? You may add a `bool` flag to your sprite class and use `std::remove_if()` with a lambda to check if a sprite is to be removed. Just for test. – Mr.C64 Nov 19 '12 at 23:35
  • @Benjamin Lindley, it crashed and said vector iterator not dereferencable. – Pengzhi Zhou Nov 19 '12 at 23:39
  • @PengzhiZhou: That definitely sounds like a problem with the way you are incrementing your iterator. Can you update your question with exactly how your loop looks now? Because the way you have it now is not as I suggested. – Benjamin Lindley Nov 19 '12 at 23:44
  • @Benjamin Lindley, your code doesn't work, because there is a function "(checkSpriteCollision(*siSprite))" in the end of loop, your ++iterator is in the loop, which means when the last element go to update and doesn't fail, will continue iterator++, then iterator was out of range and use it in the other function as well, that is why crash happens. – Pengzhi Zhou Nov 19 '12 at 23:53
  • @PengzhiZhou: The code in my answer was based on your original version, which did not have the collision check. Did you read my comment underneath your question? Where I said to put the increment at the *bottom* of the loop body? That means *after* the collision check. – Benjamin Lindley Nov 19 '12 at 23:56
  • @Benjamin Lindley, sorry.. but now I used the increment in the end of loop, but still have crash in the erase... my fault of the beginning. – Pengzhi Zhou Nov 20 '12 at 00:00
5

I'd try with the erase-remove idiom.

You may want to expose a bool attribute from the sprite class, and check if it is true to erase the sprite from the vector. Something like this (code tested with VC10/VS2010 SP1):

#include <algorithm>
#include <iostream>
#include <memory>
#include <ostream>
#include <vector>

using namespace std;


struct Sprite
{
    explicit Sprite(int spriteId)
     : kill(false)
     , id(spriteId)
    {
    }

    int id;
    bool kill;
};


int main()
{
    typedef shared_ptr<Sprite> SmartSprite;  
    typedef vector<SmartSprite> SpriteArray;

    SpriteArray sprites;
    for (int i = 0; i < 5; i++)
        sprites.push_back( make_shared<Sprite>(i*11) );

    for (auto it = sprites.begin(); it != sprites.end(); ++it)
        cout << (*it)->id << " ";
    cout << endl;

    sprites[2]->kill = true;
    sprites[3]->kill = true;

    // Remove sprites with kill flag set    
    sprites.erase
    (
        remove_if
        (
            sprites.begin(),
            sprites.end(),
            [](const SmartSprite& ps){ return ps->kill; }
        ),
        sprites.end()
    );

    for (auto it = sprites.begin(); it != sprites.end(); ++it)
        cout << (*it)->id << " ";
    cout << endl;
}

Output:

0 11 22 33 44
0 11 44
Mr.C64
  • 41,637
  • 14
  • 86
  • 162
  • Sorry maybe I am not quite understanding your codes, my codes can't combile through and said syntax error : '[' (I guess for the []), i am not quite understand your way. But thank you very much for your codes – Pengzhi Zhou Nov 20 '12 at 00:11
  • @PengzhiZhou: What compiler do you use? I've tested it using VS2010 SP1 (VC10). If there is a syntax error because your compiler doesn't understand lambdas, you can write a function instead: `bool CheckKill(const shared_ptr& ps) { return ps->kill; }` and pass `CheckKill` instead of the lambda to `remove_if()`: `remove_if(sprites.begin(), sprites.end(), CheckKill)`. – Mr.C64 Nov 20 '12 at 00:17
  • Instead of adding a bool to the class, you could just nullify the shared_ptr. Then you can use `std::remove` instead of `std::remove_if`, and you can compare against another empty shared_ptr. – Benjamin Lindley Nov 20 '12 at 00:21
  • The crash would pop out "_Block_Type_Is_Valid (pHead->nBlockUse) error". I look over on the internet and realize I might double delete something though I haven't found out, or I just allocate memory from a dead place... it hurts my head!!. thanks a lot for your guys codes and opinions. – Pengzhi Zhou Nov 20 '12 at 01:13
  • @Benjamin Lindley, Mr.C64. !! My problem solved!, the reason is in my Sprite class, I wrap it as a smart pointer, and created another smart pointer as its member variable. now I didn't use the smart pointer member variable, and the system didn't crash again.... I will continue to think about why I can't use a smart pointer inside that class, when erase sprite from vector, does it matter in his member variable? do I still need to delete that member variable if only use raw pointer instead of suing smart pointer? – Pengzhi Zhou Nov 20 '12 at 01:36
  • 1
    @PengzhiZhou: No, having a smart pointer inside a class pointed to by a different smart pointer is just fine. You likely have some other problem in your `Sprite` class. And since you didn't post the code, I can't really speculate on what it is. – Omnifarious Nov 20 '12 at 01:42
  • @Omnifarious that is why I would also suggest trying to release just a single smart_ptr. – Barney Szabolcs Nov 20 '12 at 04:26
  • @Barnabas Szabolcs, the reason I can't create a smart pointer for that object(but only can use raw pointer instead) inside my sprite, is that object can't be delete, it has a pure destroy function which was esposed by its lib, can only use object.destroy() to "delete" it? if I try delete it directly in my destructor, crash would happens. So I guess if I use a smart pointer to wrap that object, in the smart pointer underground process would try to delete it internally? that would make it crash. – Pengzhi Zhou Nov 20 '12 at 16:33
  • @PengzhiZhou: You can specify a _custom deleter_ for `shared_ptr`: `shared_ptr ptr( new T, std::mem_fun_ref(&T::DeleteMe) );`. – Mr.C64 Nov 20 '12 at 16:47
  • @PengzhiZhou: If it's a pure virtual function that means you're supposed to derive from that class and use the derived class. But you're not even supposed to be able to create an instance of the class. So you likely are already working with a derived version. I doubt that's your problem. – Omnifarious Nov 20 '12 at 17:30
  • @Omnifarious I didn't derive from that class, but instead use it as a member variable in another class, although that class is abstract class, but it still can be assigned an object using "new DiceInvadersLib("DiceInvaders.dll")->get()", this would return an instance for that abstract class. though I have no idea how they did – Pengzhi Zhou Nov 20 '12 at 19:12
  • sorry, wrong code, they are using "IDiceInvaders->createSprite("any.bmp")" to return that abstract class object – Pengzhi Zhou Nov 20 '12 at 19:19
  • @PengzhiZhou: They are creating a concrete class and return you a pointer to the base class. Inheritance! – Omnifarious Nov 20 '12 at 19:27
  • @Mr.C64, your way really can solve my problem, but as the shared_ptr is as a member variable in that class, I have to initilize it in the constructor, but it looks like I can't do this ptr( new T, std::mem_fun_ref(&T::DeleteMe) ), or this ptr.reset( new T, std::mem_fun_ref(&T::DeleteMe) ), so how can I set it in constructor? using make shared? – Pengzhi Zhou Nov 20 '12 at 20:39
  • @PengzhiZhou: What's the error code? What's your actual code? – Mr.C64 Nov 20 '12 at 22:07
  • @Mr.C64, I would like to initilize the smart pointer in the constructor, it can work as "boost::shared_ptr ptr(new T, std:mem_fun(&T:DeleteMe))", but it won't work "m_ptr(new T, std:mem_fun(&T:DeleteMe))", now what I did is created ptr first, and then assign ptr to m_ptr using m_ptr = ptr. Is any direct way to initilize m_ptr (m_ptr means member variable)? – Pengzhi Zhou Nov 20 '12 at 22:21
  • @PengzhiZhou: To initialize in the constructor, have you tried with the usual ":" syntax for member initialization in constructors: `Foo::Foo() : m_ptr( new X, .....) {...}` ? – Mr.C64 Nov 21 '12 at 08:29
3

If siSprite == m_vSprites.begin() then siSprite-1 is not legal. The usual way of writing this kind of loop is

for (siSprite = m_vSprites.begin(); siSprite != m_vSprites.end(); )
{
    rcOldSpritePos = (*siSprite)->getPosition();
    if ((*siSprite)->update())
    {
         ++siSprite;
    }
    else
    {
        siSprite = m_vSprites.erase(siSprite);
    }
}

Not sure if this will actually fix your problem because your error could be something completely unrelated.

john
  • 85,011
  • 4
  • 57
  • 81
1

Erasing potentially leads to new allocation, iterators may break there. It is better to do like this:

for(size_t i=0; i<v.size(); ++i)
  if(!v[i].update())
  {
    v.erase(v.begin()+i);
    --i; // since the elements shifted up.
  }

Otherwise you may want to use queues instead, erasing operation is very expensive on vectors.

EDIT:

I have tested it with a dummy Sprite class and works fine, the error must be within the destructor of the Sprite class.

Barney Szabolcs
  • 11,846
  • 12
  • 66
  • 91
  • I have changed my codes like yours as well, crash still happens. It looks like fail in the internal function "_Destroy(_Mylast - 1, _Mylast);" of erase. I am still looking at the codes.~ thanks for your help – Pengzhi Zhou Nov 19 '12 at 23:24
  • did you try to release just one smart_ptr? – Barney Szabolcs Nov 20 '12 at 04:19