2

Possible Duplicate:
Does std::list::remove method call destructor of each removed element?

I have a SpriteHandler class that allows the user to register a pointer to a Sprite object for drawing, all it does is access methods on the object. I wanted to write a safety catch that automatically deleted the memory associated with the pointers if the user forgot to do so by the end of the program (and it's less to worry about for the user too!) :

 

//SpriteHandler.h
class SpriteHandler {
public:
//...
    void RegisterObject(Sprite* object);
    bool IsRegistered(Sprite* object);
    void UnregisterObject(Sprite* object);
private:
//...
    static std::list<Sprite*>* _sprite = NULL;
};

//SpriteHandler.cpp
std::list<Sprite*>* SpriteHandler::_sprites = NULL;


void SpriteHandler::RegisterObject(Sprite* object) {
    if(object == NULL) return;
    if(_sprites == NULL) _sprites = new std::list<Sprite*>();
    _sprites->push_back(object);
    _sprites->sort(UDLessSprite);
}

bool SpriteHandler::IsRegistered(Sprite* object) {
    return std::binary_search(_sprites->begin(), _sprites->end(), object);
}

void SpriteHandler::UnregisterObject(Sprite* object) {
    if(object == NULL) return;
    if(IsRegistered(object) == false) return;

    _sprites->remove(object);
    if(_sprites->size() <= 0) {
        if(_sprites) {
            _sprites->clear();
            delete _sprites;
            _sprites = NULL;
        }
        return;
    }
    _sprites->sort(UDLessSprite);
}

void SpriteHandler::Release() {
    if(_sprites) {
        std::list<Sprite*>::iterator _iter = _sprites->begin();
        while(_iter != _sprites->end()) {
            delete (*_iter);
            (*_iter) = NULL;
            ++_iter;
        }
        _sprites->clear();
        delete _sprites;
        _sprites = NULL;
    }
}

 

The issue I"m having is that after the first pointer is deleted the next iterator is pointing to an already freed object (memory location is 0xfeeefeee).

How would I correctly iterate through them, deleting each one?

Community
  • 1
  • 1
Casey
  • 10,297
  • 11
  • 59
  • 88
  • 1
    Just as an aside, your call to `std::binary_search` will not give `O(log(n))` performance for a linked list - it will make `O(log(n))` comparisons, but since the list doesn't support random access iterators it will do O(n) operations all up. Maybe you know this already, but just saying in case you don't... :) – Darren Engwirda Jul 17 '11 at 06:31
  • If you are using boost, you could also use boost pointer containers (e.g. boost::ptr_list). They take ownership of the objects pointed to and delete them automatically. (http://www.boost.org/doc/libs/1_47_0/libs/ptr_container/doc/ptr_container.html) – Ferruccio Jul 17 '11 at 14:21

2 Answers2

8

If you want safety and implicit resource cleanup, do not use raw pointers, use smart pointers!

The problem with STL containers is:
If the contained object is a pointer STL containers DO NOT take ownership of destroying it. You will have to explicitly call delete on each contained pointer to delete the content it is pointing to.

Have a look at this similar question here.

The best way to go about this is not storing raw pointers inside STL containers but using their intelligent cousins smart pointers instead(boost::shared_ptr) Check out the Boost documentation, These pointer cousins are intelligent enough to deallocate themselves when there is no one referring to them and saves you the problems like the one you are facing now.

Community
  • 1
  • 1
Alok Save
  • 202,538
  • 53
  • 430
  • 533
  • You may want to link to a more recent version of Boost's documentation than 1.35. Theat was like 7 releases and several years ago. – Nicol Bolas Jul 17 '11 at 06:26
  • @Nicol Bolas: Thanks for that heads up! I updated the link. – Alok Save Jul 17 '11 at 06:56
  • 2
    I see many people advising using boost smart pointers, which is a good thing altogether, although the smart pointers are available in the C++ standard library TR1 which is implemented/supported by most compilers these days; see http://www.codesynthesis.com/~boris/blog/2010/05/24/smart-pointers-in-boost-tr1-cxx-x0/ for a comparison between boost, TR1 and C++0x and also http://www.codeguru.com/cpp/cpp/cpp_mfc/stl/article.php/c15361 for a nice hands-on introduction to TR1 smart pointers facilities(I'm sure there are more articles around, Pete Becker's ones from DDJ come to mind for example). – celavek Jul 17 '11 at 08:18
  • @celavek: That is a good point, TR1 also provides smart pointers in case one is not using `Boost`. – Alok Save Jul 17 '11 at 08:26
  • @celavek historic update: you can enjoy smart pointers in c++11, cheers – xalpha Dec 02 '13 at 19:56
1

There are a lot of things wrong with this code. But they all stem from this line:

std::list<Sprite*>* _sprite = NULL;

Unless you're using C++0x, this doesn't compile. You can't set the value of non-static members like this. Unless you intended for this to be static, and if you did, you should have used the static keyword.

But even worse is the fact that you're allocating a std::list on the heap. Why? You allocate one when you need it, and deallocate it in the destructor. Just make it a regular member variable.

C++ is not Java. Not everything has to be pointers.

If you are claiming ownership of these Sprite objects, you need to actually claim ownership of them. This would preferably be done with some kind of smart pointers. At a minimum, you should have a std::list<std::auto_ptr<Sprite> > This will ensure that the Sprite objects are deleted when you remove the entries from the list. If you have access to Boost (and if you don't, you need to get access to it) you can use boost::shared_ptr. C++0x offers much the same with std::shared_ptr.

std::auto_ptr only allows one object to own the pointer, while boost::shared_ptr allows shared ownership (hence the name). That's not necessary for your code (from what we can see at least), but it would not be a bad idea to allow shared ownership of Sprite objects. In C++0x, you should use std::unique_ptr instead of std::auto_ptr.

Either way, your code would now look like this:

//SpriteHandler.h
class SpriteHandler {
public:
//...
    void RegisterObject(Sprite* object);
    bool IsRegistered(Sprite* object);
    void UnregisterObject(Sprite* object);
private:
//...
    std::list<boost::shared_ptr<Sprite> > _sprite;
};


void SpriteHandler::RegisterObject(Sprite* object) {
    if(!object) return;
    _sprites.push_back(object);
    _sprites.sort(UDLessSprite);
}

bool SpriteHandler::IsRegistered(Sprite* object) {
    return std::binary_search(_sprites.begin(), _sprites.end(), object);
}

struct SpriteTester{
    SpriteTester(Sprite *testValue) : _testValue(testValue) {}
    bool operator()(const boost::shared_ptr<Sprite> &other) const{
        return other.get() == _testValue;
    }

    Sprite *_testValue;
};

void SpriteHandler::UnregisterObject(Sprite* object) {
    if(object == NULL) return;

    _sprites.remove_if(object, SpriteTester(object));
    //Deleting an entry cannot make the list unsorted.
}

void SpriteHandler::Release() {
    _sprites.clear();
}

Notice that I introduced the SpriteTexture. This is because you cannot pass a Sprite* to std::list::remove now that we're using smart pointers. If you do, it will wrap it in a boost::shared_ptr temporary, thus causing the pointer to be deleted. This is bad, so I had to use a custom tester.

Also, if you want to have Sprite objects registered with a class, then the Sprite object constructor (or a factory method) should be doing this registering. The user should not be able to create unregistered Sprites.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • The missing `static` is a typo, fixed. You are missing the point. All the handler does is iterate through the list and make calls to Draw methods either delegating to versions in the objects themselves or using its own versions. The user is free to register and unregister objects to be able to turn drawing on and off of specific objects at will. I don't *have* to do any cleanup whatsoever, it is simply a convenience to the user. – Casey Jul 17 '11 at 08:19
  • @Casey: If the user is free to register and unregister sprites at will, why are you deleting them when the user calls `Release`? That suggests a transfer of ownership, but it's inconsistent with the fact that the unregister function doesn't delete them. The registry either owns the pointers, shares ownership of them (in which case you need a smart pointer to govern this), or has no ownership of them at all. If the latter is true, then it should **never** delete them under any circumstances. And the latter will fail immediately after someone deletes a pointer without unregistering it first. – Nicol Bolas Jul 17 '11 at 08:36
  • Under normal conditions, if a user creates 50 sprites, they have to remember to manually delete all of them *at the end of the program* when they are no longer intended to be used. Before I decided to go this route, the SpriteHandler *didn't* take ownership and just managed the collection of pointers themselves (adding, removing and delegating). `Release` is intended to only be called at the end of the program, if the user is dumb enough to release the handler in the middle of the program, it's not my fault, they should have RTFM. – Casey Jul 17 '11 at 19:31
  • @Casey: A good API _cannot_ be used incorrectly, by design, so it is your fault. Either the SpriteHandler owns the pointers or it does not. It shouldn't sometimes delete them and sometimes not, so check the documentation for when you need to delete them. Muddled design makes your code harder to use. Furthermore, the user can delete Sprite objects even though the SpriteHandler still has a pointer to them. That is exactly what boost/std::weak_ptr was created to allow. The deletion of a Sprite can be detected by the weak_ptr, so it would be impossible for the handler to call a deleted pointer. – Nicol Bolas Jul 17 '11 at 20:40