Using vectors in C++

Started by
15 comments, last by Arreis 14 years, 10 months ago
Quote:Original post by Arreis
And thanks for pointing out that using "new" when adding objects to a vector is a memory leak, I thought vectors called destructors when erasing objects.


They do. They call the destructor of whatever type you store in them.

A pointer's destructor (to the extent that it can be said to exist) does nothing. It certainly does not call 'delete' on whatever is pointed at, because that would cause problems in almost every case. (You can create pointers to things that weren't allocated with new; in fact, you can often create pointers without even really thinking about it.)

The rule is simple: you clean up what you allocate, and the standard library cleans up what it allocates.
Advertisement
Quote:Original post by Arreis
1- Calls the normal constructor of the Block class to create a new object.
2- Calls the copy constructor on that object, and obtain a duplicate.
3- Adds that copy to the end of the vector.



Close, there's a bit more to it ...

In the code: blocks.push_back( Block(...) ); :

1- Create a temporary Block with parameters ...
2- blocks copy-constructs the temporary object
3- blocks adds the new object to the end of the list
4- The temporary Block is destructed when the function exits.

The problem is that during a Block copy-constructor (that your compiler is filling for you automatically), it's also copying the pointer to the sprite and collision box, not the actual sprite and collision box. At the beginning of step three, there are two Blocks that point to one sprite and one collision box. As soon as one of those Blocks are destructed, that one collision box and sprite are deleted, turning the other Block's pointers into invalid pointers.

Quote:Original post by Arreis
So I create new objects, and the push_back() function creates copies of them, which should be ok as long as the destructor for the original object is not called.


Yup. As the push_back() function exits, the temporary Block object used to create the copy for blocks is destroyed, deleting the pointers, and thus making the copy's pointers invalid. The simplest solution would be for Sprite to allocate a copy of the data held by the copy:

class Sprite{public:	Sprite(int x, int y);	Sprite( const Sprite& copy )	{		this->x = copy.x;		this->y = copy.y;		// this->collBox = copy.collBox; // Does not copy the object		// this->image = copy.image; // Does not copy the object		// Now this Sprite class has a collBox to call its own		this->collBox = new CollisionBox( *copy.collBox );		// It'd be safer to create the image here, instead of copying the image directly		this->image = selectColor( /* ... */ ); 	}};


Quote:Original post by Arreis
However, since all the objects are created on local context, they are deleted sooner or later and the copy is left with several inconsistent pointers. Is that correct? Is that what was happening?


If by that you mean that the temporary object passed in to push_back, then yes, that's what happens. Here are a few more things you can try:


1) Provide a copy constructor to deal with those pointers in Sprite (and/or Block, depending if those have pointers as well).
2) Use a level of indirection; use std::vector<Block*> instead of std::vector<Block>. This method would probably be better since is has more of a Java-feel to it:

typedef std::vector<Block*> BlockContainer;BlockContainer blocks;for (int i = 0; i < LINES_PER_LEVEL; i++)  for (int j = 0; j < BLOCKS_PER_LINE;  j++)    blocks.push_back( new Block(...) ); // Push back a *copy of the pointer* to the allocated memory// When accessing:blocks.front()->collBox;blocks[0]->collBox;blocks.at(0)->collBox;// When removing a block:for ( BlockContainerType::iterator it = blocks.begin(), end = blocks.end(); it != end; ++it ){  if ( /* collision */ )  {      delete *it; // Delete the memory      it = blocks.erase( it ); // Erase the node  }}// And on cleanup:for ( BlockContainer::iterator it = blocks.begin(), end = blocks.end(); it != end; ++it )  delete *it; // Delete the rest of them


Hope this helps!
It's good for OP to learn about the Rule of Three and all, but in this particular case I really don't see a reason for collBox to be allocated on the heap at all. The whole situation would be much easier to deal with if it was simply held by value.
Well, am I glad I came here to help. It's amazing how helpful and newbie-friendly you guys are. I must say, that "Rule of Three" is something I had never heard about. To be honest, as soon as I learned about C++ copy constructors, I decided to use them as little as possible. And of course, the thought of overloading the assignment operator never crossed my mind.

It may be my Java brain speaking, but I don't like handling objects directly, I find it too cumbersome to have two different ways of accessing the same object (directly and through pointer), and since using references you can pass the "real" object as an argument, I have always liked the Java way better (all pointers). I think I'll take your advice, _fastcall, and go for a pointer vector. Actually, I tried that approach before, but changed my mind afterwards. Oh, well, good ideas come and go ;)

So, thanks for your good advice, everyone, and let me ask you guys one more thing. In his first reply, _fastcall mentions that using STL's vectors for storing pointers is not such a good idea. He reccomends using boost::ptr_vector or boost::shared_ptr. The thing is, I have heard extremely good things about the Boost library. Now that you guys know what I'm up to here, more or less, do you reccomend that I use it?
Quote:Original post by Arreis
as soon as I learned about C++ copy constructors, I decided to use them as little as possible.


But you do use them quite a bit; just not explicitly. What's in question is whether you implement the copy constructor, or let the compiler generate a default copy constructor. And the answer to that is "do the work when and only when it needs to be done".

Note that with more sophisticated tools like boost::shared_ptr, you can often avoid doing the work when it would otherwise have been necessary.

Quote:and since using references you can pass the "real" object as an argument, I have always liked the Java way better (all pointers).


Of course, Java's "pointers" aren't really like C++ pointers; but then, they aren't quite like C++ references, either. And I doubt you'd still like Java so much if you had to write all the *'s and &'s that C++ makes you use to work with pointers. ;)

Quote:In his first reply, _fastcall mentions that using STL's vectors for storing pointers is not such a good idea.


It isn't.

Quote:He reccomends using boost::ptr_vector or boost::shared_ptr. The thing is, I have heard extremely good things about the Boost library. Now that you guys know what I'm up to here, more or less, do you reccomend that I use it?


Yes.
Using boost's smart pointers rather than trying to manually manage memory or even instead of implementing your own smart pointers is a very good idea and would be highly recommended.

However, storing objects by value is usually the simplest and best method where possible, as theOcelot mentioned.

But you have to understand the main limitation of boost's smart pointers, they are reference counted. This means that if they are used when circular referencing is possible you can get memory leaks.

Learning how to deal with values when coming from Java takes a bit of effort, but I think it will be worthwhile. After all, boost's smart pointers (and other RAII helper classes) work precisely because you manipulate them as values.
Well, all decided then. I'm off to read some Boost tutorials ;) And about holding the collBox by value... I suppose you're right, it's probably better that way. Oh, well, I'm still learning, right? I'm supposed to do clumsy things like this! :D

Thanks for all the help, really. See you, guys.

This topic is closed to new replies.

Advertisement