Sign in to follow this  
Phoresis

C++ Vector of Vectors problem

Recommended Posts

Having a bit of trouble using a vector of vectors:
vector< vector<Tile> > tiles;
vector< vector<Tile> >::iterator iter_ii;
vector<Tile>::iterator iter_jj;

//calls a draw method on each Tile
void display()
{
	for(iter_ii=tiles.begin(); iter_ii!=tiles.end(); iter_ii++)
	{
		for(iter_jj=(*iter_ii).begin(); iter_jj!=(*iter_ii).end(); iter_jj++)
		{
			(*iter_jj).draw();	
		}
	}
}

void readFile() 
{
//file reading stuff here

	for (int i = 0; i < rows; i++)
	{
		vector<Tile> t (columns);
		tiles.push_back(t);
	}
	for (int i = 0; i < rows; i++)
	{
		for (int j = 0; j < columns; j++)
		{
			input >> ws;
			char value;
			input >> value;
			if (value == 'k')
			{
				tiles[i][j] = Key(value, 32, 32, xpos, ypos, textures, p);
			}
			else
			{
				tiles[i][j] = Terrain(value, 32, 32, xpos, ypos, textures[0]);
			}
			xpos += 32;
		}		
		xpos = 0;
		ypos += 32;
	}
	input.close();
}

My problem is that when it calls the draw method, it seems to be calling it on the superclass Tile, which has an empty virtual function, rather than the overrided functions in the Terrain and Key classes. I can tell this because any print statement I put in the draw methods of Terrain or Key are not called. Any ideas what I am doing wrong?

Share this post


Link to post
Share on other sites
It looks like you've run into the 'slicing' problem.

Polymorphism in C++ doesn't work the way you seem to be expecting it to. To get the behavior you want, your 2D array needs to store pointers (of some sort) to Tile objects, not the tile objects themselves.

I'll go ahead and post this (as I'm guessing others have already posted by now), but I'll try to provide some more details here in a minute.

Share this post


Link to post
Share on other sites
Here's your code, revised to address the problem you mentioned (not compiled or tested):

//typedef boost::scoped_ptr<Tile> TilePtr;
// Oops, that should be:
typedef boost::shared_ptr<Tile> TilePtr;

vector< vector<TilePtr> > tiles;
vector< vector<TilePtr> >::iterator iter_ii;
vector<TilePtr>::iterator iter_jj;

//calls a draw method on each Tile
void display()
{
for(iter_ii=tiles.begin(); iter_ii!=tiles.end(); iter_ii++)
{
for(iter_jj=(*iter_ii).begin(); iter_jj!=(*iter_ii).end(); iter_jj++)
{
(*iter_jj)->draw();
}
}
}

void readFile()
{
//file reading stuff here

for (int i = 0; i < rows; i++)
{
vector<TilePtr> t (columns);
tiles.push_back(t);
}
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < columns; j++)
{
input >> ws;
char value;
input >> value;
if (value == 'k')
{
tiles[i][j].reset(new Key(value, 32, 32, xpos, ypos, textures, p));
}
else
{
tiles[i][j].reset(new Terrain(value, 32, 32, xpos, ypos, textures[0]));
}
xpos += 32;
}
xpos = 0;
ypos += 32;
}
input.close();
}


The differences between objects and pointers/references to those objects is a little much to take on in a single post, so I'll just recommend revisiting whatever C++ references you have available (or a good online reference such as the C++ FAQ Lite), and reviewing these topics, perhaps along with inheritance and polymorphism.

(I'm sure other will offer additional useful information here in this thread, but perhaps the above will at least get you pointed in the right direction in the meantime.)

[Edit: Fixed error in code.]

[Edited by - jyk on April 5, 2007 9:34:21 PM]

Share this post


Link to post
Share on other sites
Thanks for your quick reply. One question, do I have to include the boost library? Because I'm getting errors such as:

.\Main.cpp(35) : error C2653: 'boost' : is not a class or namespace name
.\Main.cpp(35) : error C2143: syntax error : missing ';' before '<'
.\Main.cpp(35) : error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
.\Main.cpp(36) : error C2065: 'TilePtr' : undeclared identifier

Share this post


Link to post
Share on other sites
Quote:
Original post by Phoresis
Thanks for your quick reply. One question, do I have to include the boost library? Because I'm getting errors such as:

.\Main.cpp(35) : error C2653: 'boost' : is not a class or namespace name
.\Main.cpp(35) : error C2143: syntax error : missing ';' before '<'
.\Main.cpp(35) : error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
.\Main.cpp(36) : error C2065: 'TilePtr' : undeclared identifier
Right, you'll need to grab the Boost libraries (much of Boost is on its way to standardization, but we're not quite there yet).

Building Boost can be a bit of a pain, but fortunately most of the libraries are header-only and don't require building. This includes all the smart pointer classes - just download the Boost libraries, add the proper include path, and you're off.

[Edit: Removed some incorrect information...]

[Edited by - jyk on April 5, 2007 9:07:05 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by jyk
[Edit: You could use std::auto_ptr instead, but scoped_ptr is a little more appropriate for the intended usage. Plus, you'll want to install the Boost libraries sooner or later, so now is probably as good a time as any :) ]

I don't believe either std::auto_ptr or boost::scoped_ptr are appropriate for using within a container. boost::shared_ptr works great, though.

Share this post


Link to post
Share on other sites
Indeed. To make this work, you'll want either a boost::shared_ptr, or a pointer-wrapper with copy semantics and objects implementing the "virtual clone idiom".

BTW, iterators have an operator-> that works just as you'd expect it to, so you don't need to do (*foo).bar nonsense. Also, we have more powerful tools at our disposal for such loops. Also, a rectangular map is more neatly expressed using (if we're reaching for Boost anyway) a boost::multi_array than a vector of vectors. Also, there's normally no need to .close() streams explicitly. Also, why would you use a global here for the tile map? Also, there are neater ways to handle 'xpos' and 'ypos' like that, by using the comma operator in the for loop conditions (and thus scoping those variables properly).

Not tested, but this should be more or less it:


typedef boost::shared_ptr<Tile> TilePtr;

typedef boost::multi_array<TilePtr, 2> Tilemap;

void drawTile(const TilePtr& ptr) {
ptr->draw();
}

void display(const Tilemap& map) {
std::for_each(map.begin(), map.end(), drawTile);
}

Tilemap readFile(const std::string& name) {
ifstream input(name.c_str());
int rows, columns;
input >> rows >> columns;
Tilemap result(boost::extents[rows][columns]);

for (int i = 0, ypos = 0; i < rows; i++, ypos += 32) {
for (int j = 0, xpos = 0; j < columns; j++, xpos += 32) {
char value;
input >> ws >> value;
if (value == 'k') {
result[i][j] = TilePtr(new Key(value, 32, 32, xpos, ypos, textures, p));
} else {
result[i][j] = TilePtr(new Terrain(value, 32, 32, xpos, ypos, textures[0]));
}
}
}
return result;
}




EDIT: More information about the problem you were having initially.

Share this post


Link to post
Share on other sites
Quote:
Original post by Agony
I don't believe either std::auto_ptr or boost::scoped_ptr are appropriate for using within a container. boost::shared_ptr works great, though.
Right, that should have been shared_ptr in my example, not scoped_ptr. Sorry about that.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this