Archived

This topic is now archived and is closed to further replies.

Peon

Question about virtual functions

Recommended Posts

I was under the impression that if you declared a memeber function 'virtual' in a base class and called it from subsequenent derived classes, it would use the definition in the drived class. Indeed, this is the case. However, if you make an array of the base class and try and call the virtual function, it reverts to the definition in the base class. Is there anyway I could do something like this?
//two different classes derived from cGeneric

cObjectA A;
cObjectB B;

cGeneric List[2];
List[0] = &A;
List[1] = &B;

//now we call a virtual member function

List[0].Write();
List[1].Write();
In the above example, the Write() function would simply take whatever definition it has in cGeneric. *I* want it to take the one in the derived class. Is there any way to do this, or do I need to maintain two separate, specific lists, one for each object (or as many lists as different objects I have)? [edited by - Peon on October 8, 2003 4:29:27 PM]

Share this post


Link to post
Share on other sites
quote:
Original post by superdeveloper
also, just to confirm, should it not be? :

cGeneric * List[2];

... and ...

List[0]->Write();
List[1]->Write();


Perhaps, maybe I wrote it wrong I still get confused a bit when I work with pointers/addresses, etc.. The above code MAY have errors in it.

Share this post


Link to post
Share on other sites
Well, for one thing you''re getting mixed up with pointer syntax...

but assuming that you want List to be an array of simple cGeneric objects, that won''t work. In C++, polymorphism only works with pointers to base classes, not actual instances of them.

Share this post


Link to post
Share on other sites
quote:
Original post by twix
Well, for one thing you''re getting mixed up with pointer syntax...

but assuming that you want List to be an array of simple cGeneric objects, that won''t work. In C++, polymorphism only works with pointers to base classes, not actual instances of them.

Yes I know I''m still getting than hand of it and rely a little too much on the compiler to help me out. But anyway..

I suspected as much, so I guess instead of a nice big list of objects, I''ll have to make a separate list for each type?

Share this post


Link to post
Share on other sites
Use pointers in this case.... that will work properly. The problem is.. if you define a class like this:


class baseClass
{
public:
int x;
int y;
virtual void Set(int num) { x = num; };
};

class Derived1 : public baseClass
{
virtual void Set(int num) { y = num; };
};

class Derived2 : public baseClass
{
virtual void Set(int num) { x = y = num; };
};


Doing a sizeof(baseClass) or any of the derived classes will yield an 8 (size of 2 ints). When you do it the way your application does:

Derived1 d1;
Derived2 d2;
baseClass Array[2];

Array[0] = d1;
Array[1] = d2;

Array IS still a baseClass, you just copied the DATA (x and y in this case) into it, so any functions you call will belong to baseClass. Now, if you use pointers:

Derived1 d1;
Derived2 d2;
baseClass *Array[2];

Array[0] = &d1;
Array[1] = &d2;

Then it WILL call the proper virtual function, as it's setting it's physical address to that of a derived type, so it will use it's functions rather than just copy it's data. Hope this helps a little bit .

--- Edit ---
One last note...

must use Array[0]->Set() rather than Array[0].Set(), since it's a pointer.


[edited by - Ready4Dis on October 8, 2003 5:09:58 PM]

Share this post


Link to post
Share on other sites
This issue is discussed (thoroughly) in Scott Meyers'' Effective C++ (Item 39). The List.operator[] returns a pointer of type cGeneric, which is why that one gets called. To "fix" this, people often simply downcast the List[] to the desired derived cast (cast down the inheritance hierarchy). This works, BUT Meyers points out that this is bad practice as it leads to a code-maintenance nightmare. He then goes on to give examples of problems you might have and how to fix them. However, it seems the "correct" fix (avoiding downcasts) requires rearranging the inheritance or changing your lists.

But I don''t have a full grasp on this yet, so I''m not sure if there''s another way. The new casting features (I think added to C++ with RTTI may be a way to do it, but I haven''t read about it yet).

Search some for C++ downcast - might find something.
Here''s a couple that look good (though I''ve only skimmed them):
Casting Basics
CPP Home Tutorial

Tadd
- WarbleWare

Share this post


Link to post
Share on other sites
quote:
Original post by Peon
quote:
Original post by twix
Well, for one thing you''re getting mixed up with pointer syntax...

but assuming that you want List to be an array of simple cGeneric objects, that won''t work. In C++, polymorphism only works with pointers to base classes, not actual instances of them.

Yes I know I''m still getting than hand of it and rely a little too much on the compiler to help me out. But anyway..

I suspected as much, so I guess instead of a nice big list of objects, I''ll have to make a separate list for each type?





No, just use a list of pointers rather than a straight list, this way it points to the proper derived type, otherwise doing an = to another type simply copies over it''s data members, not it''s virtual function addresses. When using the pointer method, it points to the derived type, and when you call it''s functions, it uses the proper virtual function address (which is stored in what''s called a vtable, so when you point to the proper type, it uses it''s local vtable rather than the base classes vtable, and when you don''t use a pointer, it''s still the baseClass type just copies the data over and uses the baseClass vtable).

Share this post


Link to post
Share on other sites
Thanks for the idea reagrding the array of pointers; it did exactly what I needed.

What I worry about though is that I will run out of memory, since I'm not sure how to use delete (in this case). Let me give an example:


void Input()
{
case FIRE:
cProjectile* NewBullet = new cProjectile;
NewBullet->LoadParameters("generic.prj");
AddObject(NewBullet);

//just soem debugging

//cMessage<MessageType, int, int> BulletMsg;

//BulletMsg.SetMessage(GET_LIFE, 0, 0);

}

In this example, I have created an input handling routine in my engine. If the user presses a button (FIRE, in this case), a new cProjectile is created. This is then added to a database of all the objects. I can't delete this bullet right after, because otherwise I will lose the values of it, when I need to maninpulate it from within the database. If I don't call delete, I have a memory leak, which is equally undesirable.

Basically what I have (now) is an array of pointers to all my objects. In this case, how would I go about deleting objects that I no longer need?

Sorry in advance for the new questions; I'm trying to build a good database class that is not game-specific, that I can use for many of my future projects.


[edited by - Peon on October 8, 2003 8:11:08 PM]

Share this post


Link to post
Share on other sites
Ah, you have stumbled upon one of the most challenging and annoying game programming design issues: memory management. There are a number of approaches to this. If you keep your overall memory usage fairly low, you can use the most basic form of garbage collection and simply empty your databases at regular intervals (between levels and such), and then clean them all out at the end of the game.

Otherwise, you're going to have to come up with a reference-counting scheme, where you keep track of how many pointers there are to a certain object and delete it when there are no more. Boost's smart pointer is very good for this.

If you want to go all out, you can set up your own memory allocation system (i.e. allocate 50MB of memory at the start and don't use new and delete during your actual game). This can be faster if you know what you're doing, and this is the approach a lot of commercial games take.

Basically, this is the price you pay for using a language that asks you to manage your own memory.

You can check out superpig's Enginuity articles for the source to a simple memory manager.

[edited by - twix on October 8, 2003 9:17:37 PM]

Share this post


Link to post
Share on other sites
Thanks for the Engenuity links; I hadn''t seen those article before.

As far as deleting objects, I actually meant in a more literal sense -- the keyword ''delete''. If I use new to create an object, inside of a function, and then pass the address of that object to a database, can I EVER ''delete''that particular object? Or is the ability to ''delete'' forever lost once I leave the function?

Share this post


Link to post
Share on other sites
quote:
Original post by Peon
As far as deleting objects, I actually meant in a more literal sense -- the keyword ''delete''. If I use new to create an object, inside of a function, and then pass the address of that object to a database, can I EVER ''delete''that particular object? Or is the ability to ''delete'' forever lost once I leave the function?

Yes, that is what I was talking about. If you allocated something with new, you can delete it from anywhere, later. The problem is deciding when and where you can call delete on it without leaving outstanding pointers that, when accessed, will cause a segfault.

Share this post


Link to post
Share on other sites
Ok, little primer here. As far as I know, most databases handle their own memory allocation. This means that the database decides when to fetch a data value from disk into memory. When you create an object and give it to the database it most likely just makes a copy anway. This means that your new''ed value is probably irrelevant at this time. Databases probably always return a copy as well. This means that the database stores what you tell it to store and retrieves what you tell it to retrieve, but nothing else.

So, in your scheme I''d make the bullet a local declaration (no new), then I''d store the bullet in the database; the process of doing so should copy the bullet parameters from function local storage into the database''s managed storage. After this you don''t need to worry about memory management because the local bullet will lose scope once you exit the function. Then in another function when you need to use the bullet you fetch it out of the database. It is probably still cached anyway.

I would question putting things like bullets into a database though. It''d probably be smarter to store outstanding bullet objects in something like a large data structure which would allow access via pointers. Then I''d have my databases updated infrequently depending on the risk. I guess it all depends on what type of game you''re making. If you are putting them into a large data structure of some sort then you''d probably want to new them before you insert them and then delete them when you are through using them or when you destroy the data structure.

RandomTask

Share this post


Link to post
Share on other sites
twix: OK, assuming I have created a 'new' object in a function and now the address of it is stored in my database (specifically, an array of generic objects), how would I delete the object? Would I simply 'delete' the object at the correct index in the database (ie: delete ObjectList[0])? I guess I that while I kind of know when to use delete, I don't really understand what is actually DOES. Does it free the memory at the address of the variable you specify?

RandomTask: I'm probably not understanding correctly, but I think the reason I can't just copy parameters over is that since I would be copying them into a generic list of objects, I would lose my virtual functions. That's what I originally tried to do (I think).

The reason I wanted to put everything in a big list is because I like the idea of everything being organized like that, and I think it will be easier to do things like collision detection, and whatnot. It also means that I should have no problem adding new objects of the same base class, should I use the database for another project.

Thanks for the help so far

[edited by - Peon on October 9, 2003 1:58:23 PM]

Share this post


Link to post
Share on other sites
quote:
Original post by Peon
twix: OK, assuming I have created a 'new' object in a function and now the address of it is stored in my database (specifically, an array of generic objects), how would I delete the object? Would I simply 'delete' the object at the correct index in the database (ie: delete ObjectList[0])? I guess I that while I kind of know when to use delete, I don't really understand what is actually DOES. Does it free the memory at the address of the variable you specify?

You have it right. delete [pointer] frees the memory that the pointer points to (provided that it was allocated with new at some point).
quote:

I would question putting things like bullets into a database though.

As would I.

Fortunately, when Peon says "database", he means "big list".

[edited by - twix on October 9, 2003 9:26:04 PM]

Share this post


Link to post
Share on other sites
quote:
Original post by twix
Fortunately, when Peon says "database", he means "big list".

[edited by - twix on October 9, 2003 9:26:04 PM]
LOL, aren''t I the n00bar I just called it a database because in my cManager class (which I consider a database) it has a protected list of objects, but you can add, remove, allocate memory. I hope to add the ability to sort the list by key values, etc... In that sense, I would consider it (the manager) a database. If not, then what would be considered a database (even though it''s a little OT)?

Thanks for the confirmation on delete. I wrote some more console programs to test it out and it worked pretty much like I thought.

Share this post


Link to post
Share on other sites
quote:
Original post by Peon
LOL, aren''t I the n00bar I just called it a database because in my cManager class (which I consider a database) it has a protected list of objects, but you can add, remove, allocate memory. I hope to add the ability to sort the list by key values, etc... In that sense, I would consider it (the manager) a database. If not, then what would be considered a database (even though it''s a little OT)?

It''s fair enough to consider that a database, but if you carefully read RandomTask''s post, he''s clearly talking about serious database software like MySQL.

Share this post


Link to post
Share on other sites
The simple answer to the original question is:

Look at this line

cGeneric List[2]; 

Of course you''re going to get the base class version of the code; you''ve declared an array of base class objects! Virtual functions ONLY take effect when you use a pointer to a base class object to point to an object which is derived from the base class.

Share this post


Link to post
Share on other sites
Twix,

Thanks for clearing that up. MySQL, etc, are exactly what I was talking about.

Peon, even with search by value, etc I still wouldn''t call it a database. Maybe you should use a data structure called a map or a hash. These generally will do find by value and by key and they are optimized to use special low search time algorithms, unfortunately they aren''t really ordered but I doubt if your application would require ordering.

Traditionally the term database applies to something that manages persistence (writing to disk and reading from disk) if I''m not mistaken.

RandomTask

Share this post


Link to post
Share on other sites