Sign in to follow this  
Martijnvdc

Virtual functions and vectors

Recommended Posts

I am trying to write a simple physics engine but i have a problem. I tried to reproduce the problem with this code. If i know how to solve THIS i know how to solve it in my simple physics engine:
#include <cstdlib>
#include <iostream>
#include <vector>
using namespace std;
   
class Animal
{
  public:
  virtual void WhatAmI(){cout<<"i am an animal!"<<endl;};      
};
            
class Bird : public Animal
{
      public:
      void WhatAmI(){cout<<"i am a bird!"<<endl;};   
}; 
                         
class World
{
private:
  std::vector<Animal> Animals;
public:
  void AddAnimal(Animal& a) { Animals.push_back(a); }
  Animal GetAnimal(int index){return Animals[index];}
};  

int main(int argc, char *argv[])
{
    Animal a;
    Bird b;
    World w;
    w.AddAnimal(a);
	w.AddAnimal(b);
	w.GetAnimal(0).WhatAmI();
	w.GetAnimal(1).WhatAmI();
    system("PAUSE");
    return EXIT_SUCCESS;
}

result: I am an animal! I am an animal! The problem is that w.GetAnimal(1) should be a bird, not just an animal. How can i get the vector to remember what kind of animal it is? Thanks in advance, sorry for my bad English :)

Share this post


Link to post
Share on other sites
When dealing with values (as opposed to references), C++ always knows the exact type at compile time. You cannot have a polymorphic value.

What is happening is slicing: when "b" is passed to AddAnimal() only the Animal portion is copied into the vector, the rest is lost.

You need to store (smart) pointers in your vector. Or you can use a modified vector class (like boost::ptr_vector) that will take ownership of the pointers contained therein.

Share this post


Link to post
Share on other sites
Ya. Its slicing.

STL containers use value semantics. That means when you insert an item into your vector, it actually ends up storing a copy of the object passed in. The problem is that your vector is parameterized to hold instances of your base class. This means when you insert an item, your base class copy constructor is invoked. Of course the copy constructor in your base class only knows how to copy its base class parts, and as a result it simply discards all the "data" specific to the passed in derived type.

To fix this you can use a pointer container. These are containers which can safely hold pointers to items on the heap and resolves you of the responsibility of having to manually free them. Check this out for more info.

Share this post


Link to post
Share on other sites
Thanks for your replies!
Now the output is still the same and i can't figure out what i am doing wrong this time :p

#include <cstdlib>
#include <iostream>
#include <vector>
using namespace std;

#include "ptr_vector.h"
using namespace stdx; // for ptr_vector, ptr_vector_owner

class Animal
{
public:
virtual void WhatAmI(){cout<<"i am an animal!"<<endl;};
};

class Bird : public Animal
{
public:
void WhatAmI(){cout<<"i am a bird!"<<endl;};
};

class World
{
private:
ptr_vector<Animal> Animals;
public:
void AddAnimal(Animal *a) { Animals.push_back(a); }
Animal GetAnimal(int index){return Animals[index];}
};

int main(int argc, char *argv[])
{
Animal* a = new Animal();
Bird* b = new Bird();
World w;
w.AddAnimal(a);
w.AddAnimal(b);
w.GetAnimal(0).WhatAmI();
w.GetAnimal(1).WhatAmI();
system("PAUSE");
return EXIT_SUCCESS;
}


Share this post


Link to post
Share on other sites
Quote:
Animal GetAnimal(int index){return Animals[index];}


You take something out of container, and convert it into Animal.

If something at index happens to be a Bird, the above is effectively the same as:
Animal GetAnimal(){
Bird b;
return Animal(bird);
}


You need to return a reference to Animal:
 Animal & GetAnimal(int index){return Animals[index];}

Share this post


Link to post
Share on other sites
Hi Martijnvdc,

The only change in ur program u have 2 make is change the objects to the pointer to the objects.... So the code becomes.....




#include <cstdlib>
#include <iostream>
#include <vector>
using namespace std;

class Animal
{
public:
virtual void WhatAmI(){cout<<"i am an animal!"<<endl;};
};

class Bird : public Animal
{
public:
virtual void WhatAmI(){cout<<"i am a bird!"<<endl;};
};

class World
{
private:
std::vector<Animal*> Animals;
public:
void AddAnimal(Animal* a) { Animals.push_back(a); }
Animal* GetAnimal(int index){return Animals[index];}
};

int main(int argc, char *argv[])
{
Animal* a= new Animal();
Bird* b= new Bird();
World w;
w.AddAnimal(a);
w.AddAnimal(b);
w.GetAnimal(0)->WhatAmI();
w.GetAnimal(1)->WhatAmI();

return EXIT_SUCCESS;
}



When u use pointer to objects its memory will be allocated at runtime(as opposed to the objects which get allocated at compile time).

Thanks and Regards,
Rohith.H.N

Share this post


Link to post
Share on other sites
Quote:
Original post by Rohithzhere
When u use pointer to objects its memory will be allocated at runtime(as opposed to the objects which get allocated at compile time).


Actually, stack objects get allocated at runtime if they are not in the global scope. Global stack objects get constructed at runtime (right before main is called), but their data is allocated in the data seg. Hence allocated at compile time, constructed at runtime.

The point is - the problem you solved had nothing with when/how the objects were constructed. The crux of the problem was in providing std::vector a value instead of a reference.

In other words, this will yield the same results:


...
std::vector&lt;Animal*&gt; Animals;
...
Animal a;
Bird b;
World w;
w.AddAnimal(&a);
w.AddAnimal(&b);

Share this post


Link to post
Share on other sites
Kind of the same. But (to the OP) watch out for this:


void f()
{
Bird b;
Wombat w;

some_non_local_vector.push_back(&b);
some_non_local_vector.push_back(&w);
}

// function exits, b and w are destroyed but vector still contains pointers,
// which now point to garbage memory

Share this post


Link to post
Share on other sites
Yes, i was confronted with that problem a while ago
I didn't have a clue how to fix it for like half an hour :D
But i found it eventually...

void SetupEngine()
{
phWorld.AddBody(new CphBall(Vector2f(300.0f,300.0f),300.0f));
}

This is how it works now, i don't have to define a global 'CphBall', i just use 'new' :p
(this code is from the so called 'physics engine' i try to make)

Share this post


Link to post
Share on other sites
Personally I would change your code to the following which uses boost's shared pointer and returns references to constant Animals. The reason for the constant references is to do away with some of the pointer syntax and not to allow code to change the animal.

#include <iostream>
#include <vector>
#include <boost/shared_ptr.hpp>

class Animal;
typedef boost::shared_ptr<Animal> Animal_ptr;
class Animal
{
public:
virtual void WhatAmI()const{std::cout<<"i am an animal!"<<std::endl;};
};

class Bird : public Animal
{
public:
void WhatAmI()const{std::cout<<"i am a bird!"<<std::endl;};
};

class World
{
private:
std::vector<Animal_ptr> Animals;
public:
void AddAnimal(Animal_ptr a) { Animals.push_back(a); }
Animal const& GetAnimal(int const& index)const {return *Animals[index];}
};

int main()
{
World w;
w.AddAnimal( Animal_ptr(new Bird) );
w.AddAnimal( Animal_ptr(new Animal) );
w.GetAnimal(0).WhatAmI();
w.GetAnimal(1).WhatAmI();
}



Share this post


Link to post
Share on other sites
Quote:
Original post by Martijnvdc
Yes, i was confronted with that problem a while ago
I didn't have a clue how to fix it for like half an hour :D
But i found it eventually...

void SetupEngine()
{
phWorld.AddBody(new CphBall(Vector2f(300.0f,300.0f),300.0f));
}

This is how it works now, i don't have to define a global 'CphBall', i just use 'new' :p
(this code is from the so called 'physics engine' i try to make)


Just remember that since you new-ed it, it is your responsibility to delete it. This is (urgh, urgh) not really necessary if it is only created once and and needs to be deleted at program termination (urgh, urgh) as your OS will almost certainly reclaim memory used by your program at termination, but if you are allocating in a loop, or whatever, you'll end up leaking memory.

But forget that and delete what you new. Or use a container or smart pointer, as mentioned above, that takes ownership of the pointers and deletes them when the container goes out of scope.

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