Sign in to follow this  
ekrax

storing derived abstract classes

Recommended Posts

this is sorta a follow up post, on a question i had in the beginner forum, but i think its a little off topic of that post so i decied to make a new post. anyways i decided to give the code to what i was trying to create (a way to store derived type instances in a base class array (not really an array)) ... im amazed this actually works, so many things now possible, im pretty sure though people have used this before, or maybe they havent.
#include <iostream>
#include <vector>

class base
{
	public:
		virtual void show () = 0;
};

class d1 : public base
{
		int x;
	public:
		void show ()
		{
			std::cout << x << std::endl;
		}
		d1 (int X) : x (X) {}
};

class d2 : public base
{
		double k;
	public:
		void show ()
		{
			std::cout << k << std::endl;
		}
		d2 (double K) : k (K) {}
};

class d3 : public base
{
		char c;
	public:
		void show ()
		{
			std::cout << c << std::endl;
		}
		d3 (char C) : c (C) {}
};

class container
{
		std::vector <base *> refrences;
	public:
		template <class type> void add (type object)
		{
			type *p = &object;
			refrences.push_back (p);
		}
		void execute ()
		{
			for (int n = 0; n < refrences.size (); n ++)
				refrences[n] -> show ();
		}
                  ~container ()
		{
			for (int n = 0; n < refrences.size (); n ++)
				delete refrences[n];
		}
};

int main ()
{
	container stuff;
	stuff.add <d1> (9);
	stuff.add <d2> (5.6343);
	stuff.add <d3> ('g');
	stuff.add <d2> (12.111);
	stuff.execute ();
}


well since you cant create an array, or vector, or a list of derived type objects i created a container class of my own which creates an instance of an object and then creates a pointer to it and stores it withen a vector of refrences of base class types. if the base class contains only virtual functions you can easily manipulate all dervied class types with one container. im just wondering if this is a common sort of thing to do in programs with lots of inheritance and abstract classes. also is there any potential problems with using this? thanks for any suggestions.

Share this post


Link to post
Share on other sites
this code:


template <class type> void add (type object) {
type *p = &object;
refrences.push_back (p);
}


should be ringing fire bells in your head your gonna get dangling pointer syndrome.

Share this post


Link to post
Share on other sites
Your add() function receives a parameter "object" which is passed by value. This means that the function gets to work on a copy of the argument, and this copy disappears at the end of the function. BUT, you put a pointer to this copy into the vector, so as soon as the function ends, the vector will contain a pointer to invalid memory.

What I like to do is to give such add functions pointers. This has two advantages:
- you are not forced to make a permanent copy for internal use (as would be the case for a pass-by-value)
- you don't need a templated function anymore, since you can pass an object of type base* to the function.

Besides that, I consider this to be a very common practice as far as polymorphism goes.

EDIT: also, the above should not compile.

stuff.add <d1>() expects an object of type d1, but you pass it 9, which is not of type d1.

But aside from your implementation errors, your method is perfectly valid.

Share this post


Link to post
Share on other sites
I haven't read all the code, so bear me.

No, this is the most common way. Lots of game objects are derived from a base CGameObject class where they all replace the virtual functions. Since C++ classes have a VTable(It's a table with function pointers to the correct functions) casting down a virtual class won't remove the functions.

That's exactly how I am going to do my top-down shooter, I derive all objects from IRenderable and stop them in a list of IRenderable pointers.

However, your container can be much simpler:


class Base
{
public:
virtual void Write() = 0;
};

class Ship : public Base
{
public:
void Write() { std::cout << "I'm a ship!" << endl; }
};

class Car : public Base
{
public:
void Write() { std::cout << "I'm a car! Vr00m Vr00m!" << endl; }

};


int main()
{
std::vector<Base *> Objects;
Ship theShip;
Car theCar;
Objects.push_back((Base*) &theShip);
Objects.push_back((Base*) &theCar);

Car *ptr = (Car *)Objects[0]; // Get the ship
ptr->Write(); // Outputs "I'm a ship"
}



It's called polymorphism.

Toolmaker

Share this post


Link to post
Share on other sites
It is a fairly common thing - but it is still good to lear that kind of stuff alone.

Now, for the implementation part, you add function do no instanciate any object. It is more than dangerous - looks like your code works using some kind of magic here... Very strange...

You'll have to do :


template <class type> void add (type object)
{
type *p = new type(object);
refrences.push_back (p);
}



or

template <class type> void add (type *object)
{
refrences.push_back(object);
}
// ...
stuff.add <d1>(new d1(9));



Then you'll be free from that "dangling pointer syndrome" :)

HTH

Share this post


Link to post
Share on other sites
Quote:
Original post by Toolmaker

std::vector<Base *> Objects;
Ship theShip;
Car theCar;
Objects.push_back((Base*) &theShip);
Objects.push_back((Base*) &theCar);



You don't need to cast derived types.

Quote:
Original post by Toolmaker
Car *ptr = (Car *)Objects[0]; // Get the ship
ptr->Write(); // Outputs "I'm a ship"


And since Base is a polymorphic type, you can polymophically call write so there is no need for that so this is fine:


Objects[0]->Write();


Oh and thanks to who ever ranked me down for stating a fact about the problem with that "add" function of ekrax.

Share this post


Link to post
Share on other sites
woah it wasnt intentional to not use new, it is by accident that i didnt use new.

this is why i used a destructor to delete everything i initialized.

Quote:

looks like your code works using some kind of magic here... Very strange...


this is magic, how the hell did this comple? im using borland command line compiler v5 mayeb they have a magic 'fix stupid programming errors' programmed into their compiler ...

Share this post


Link to post
Share on other sites
Just thinking about such code as this:


template <class type> void add (type object)
{
type *p = new type(object);
refrences.push_back (p);
}


or


template <class type> void add (type *object)
{
refrences.push_back(object);
}
// ...
stuff.add <d1>(new d1(9));


Is abit redundant as you only wont to add sub-types of "base" by writing a template function your saying any related or unrelated type can be added.

This is all you need to add all sub-types of base:


void add(base& b) {
references.push_back(&b);
}


or

void add(base* b) {
references.push_back(b);
}

Share this post


Link to post
Share on other sites
Quote:
Original post by snk_kid

void add(base& b) {
references.push_back(&b);
}


or

void add(base* b) {
references.push_back(b);
}



Or even better use the virtual copy constructor idiom and do this:


void add(const base& b) {
references.push_back(b.clone());
}

Share this post


Link to post
Share on other sites
that would make more sense to do it that way, but i cant get it to work.

void add (base *object)
{
base *p = new base;
*p = object;
refrences.push_back (&p);
}

doesnt work if i use something like ...
stuff.add (d1 (9));

it wont compile, says i cant create an instance of a base class type here ... but i can use new when it is a template function.

Share this post


Link to post
Share on other sites
ok i forgot that i can't create an instance of an abstract class that contains pure virtual functions ... so now how do i create an instance to get a refrence if its not a template function?

Share this post


Link to post
Share on other sites
Quote:
Original post by ekrax
that would make more sense to do it that way, but i cant get it to work.

void add (base *object)
{
base *p = new base;
*p = object;
refrences.push_back (&p);
}


One you can't instantiate abstract classes only concreate sub-types. Also you don't need to take the addresss of the pointer to assign to a pointer, and if your actually gonna instantate with-in that function then this is where you need the virtual constructor idiom.

Quote:
Original post by ekrax
doesnt work if i use something like ...
stuff.add (d1 (9));


That is asking for trouble unless the parameter was a constant reference to type base.

Share this post


Link to post
Share on other sites
ok i got ya, now though looking back at the template version im confused about this ...

stuff.add <d1> (9);

however the add function is like this ...

template <class type> void add (type object);

however im passing an integer to the function add but yet it still works somehow, then i changed the function call to this ...

stuff.add <d1> (d1 (9));

so now im actually sending a class type to it not just an integer, but then this line somehow works ...

type *p = new type (object) ...

isnt this using the entire object as the constructor? the constructor expects an int and this sends itself as an object as the constructor.

jesus this is magic code!

Share this post


Link to post
Share on other sites
Quote:
Original post by ekrax
ok i forgot that i can't create an instance of an abstract class that contains pure virtual functions ... so now how do i create an instance to get a refrence if its not a template function?


Okay with your code from above and using virtual constructor idiom to achieve what was trying to achieve:


#include <vector>
#include <iostream>

struct base {

virtual base* clone() const = 0;
virtual base* create() const = 0;

virtual void show () const {
std::cout << "base\n";
}
virtual ~base() {}
};

struct derived1 : base {

base* clone() const { return new derived1(*this); }
base* create() const { return new derived1; }

void show() const {
std::cout << "derived1\n";
}
};

struct derived2 : base {

base* clone() const { return new derived2(*this); }
base* create() const { return new derived2; }

void show() const {
std::cout << "derived2\n";
}
};


class container {
std::vector<base *> references;
public:
void add(const base& b) {
references.push_back(b.clone());
}

void execute () const {
for(int i = 0, n = references.size(); i < n; ++i)
references[i]->show();
}

~container() {
for(int i = 0, n = references.size(); i < n; ++i)
delete references[i];
}
};

int main () {

container stuff;
stuff.add(derived1());
stuff.add(derived2());
stuff.execute();

return 0;
}

Share this post


Link to post
Share on other sites
Quote:
Original post by ekrax
so now im actually sending a class type to it not just an integer, but then this line somehow works ...

type *p = new type (object) ...


Well thats fine because your copy constructing the parameter instance on the heap, any instance created with new will persist through out any scope but your responsiable to delete it to give it back.

Share this post


Link to post
Share on other sites
just let you know i made some mistakes in that code earlier on so i've corrected it now, anyways you don't have to use the virtual constructor idiom if you don't wont to create copies.

Share this post


Link to post
Share on other sites
This line works because of what is called a copy constructor:

Object::Object( const Object & )

You do not need to write it: if you don't, it is replaced by a default copy constructor that performs a simple (shallow) copy of its argument.

Share this post


Link to post
Share on other sites
ok yeah i should have realized there was a default copy constructor there. usually when i think of copy constructors i think of passing classes to functions, but i didnt realize that they can be used to initialize objects aswell.

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