![]() |
| |||||||
![]() |
| | LinkBack | Thread Tools | Display Modes |
| | #1 |
| Absent Minded Programmer Join Date: May 2005
Posts: 933
| I believe I've found an official Answer to my Problem (Object Factories) Always I had a few questions, how the heck do I create new instances of objects without writing 30 lines of code (probably a hundred or so in some cases). How do I pass an "Object" to a function that simply pushes the object into a vector, while being able to send all of the class "Object" derivedrens unique? Ultimately, Passing *Object to the function while *Object might be a space ship, a wall, a planet, an asteroid, a tree, etc etc... The answer is an Object Factory. An Object factory that creates other Object Factories, a super factory to manage -EVERY- object in your entire program. This "Object Factory" would be at the very very very VERY tip top of your Include files, included once into the entire project, but used and weaved in throughout, in amazingly seemless OOP. I believe this is the HOLY GRAIL of oop, the pinnacle of C++ as an object oriented language, and to boot, not that difficult to understand the outlying structure of the whole thing. I will include an example of a simple Object Factory below.. The article was written by Robert Geiman and is on www.GameDev.net . Props
__________________ Sometimes I forget what I am doing when I enter a room, actually, quite often. Last edited by Shamino; 12-16-2005 at 06:05 PM. |
| Shamino is offline | |
| | #2 |
| Deprecated Join Date: Oct 2004 Location: Canada
Posts: 1,032
| The actual OO part of that design is pretty standard and not to the max at all (theres tons of design patterns, and standard OO features (multiple inheritance, etc) that it doesnt use. It is definetly close to the max it could go on this feature for sure (macro magic, templates, classes, oo, maps, etc.). God, I hate when macro magic is necessary.. The point of this is so that it returns the right subclass type, so it shouldnt be assigned to the base class Monster, it should be assigned to the subclass. So it should actually be this: Code: GoblinMonster *monster1 = monster_factory.Create(GOBLIN_MONSTER, 34.5f, 63.0f, 7.7f); OgreMonster *monster2 = monster_factory.Create(OGRE_MONSTER, 83.0f, 14.2f, 54.4f); Your examples kind of fail to show why this is useful at all. Of course you have a system to handle all objects (thats obvious). However instead of returning the base class Object, you can return the correct subclass. You've found the article on how to do that. In order to utilize this you would have to have to do sort of what the Create() method does, and search for the correct subclass by id, but instead of returning NULL, when you find it you return the subclass. So then simply add a new method to class ObjectFactory: Code: BaseClassType *Find(UniqueIdType unique_id)
{
Iterator iter = m_object_creator.find(unique_id);
if (iter == m_object_creator.end())
return NULL;
return (*iter).second;
}
The reason you're seeing the constructor for the OgreMonster is because it actually does exist in the map in the ObjectFactory, but when you assign it to Monster you arent going to be able to access functions that OgreMonster has but Monster does not. Try it out and please let me know if I'm wrong, because that would be great if I was wrong - the problem would be solved. I can't figure out how it would work. Of course you can get the subclass you want, but you could do that anyway - that isn't the problem. The problem is the recieving end of a function. Now that you see you cant assign it to the Monster class, that should be apparent. You would have to know what class to assign it to (OgreMonster or GoblinMonster) in the function you are passing say the ID or object to. Which is the whole problem in the first place and that makes this factory pointless as a solution (its a nice factory, but I dont see it being a solution to this problem). You could template a function accept the Ogre type. But thats just annoying and I'd rather just stick all of the information CRender, CWorld, and CPhysics need in Object and be done with this problem. EDIT: I can't compile it to test anyhow, what compiler do you use? EDIT: You could do the most valuable test by keeping monster1/2 as type Monster. Then simply give OgreMonster and GoblinMonster two different functions, and then calling monster1->function1(), and monster2->function2(), and see if the compiler tells you monster1/2 don't contain those functions (like it normally would).
__________________ Warning: Have doubt in anything I post. GCC 4.5, Boost 1.40, Code::Blocks 8.02, Ubuntu 9.10 010001000110000101100101 Last edited by Dae; 12-16-2005 at 08:50 PM. |
| Dae is offline | |
| | #3 |
| Absent Minded Programmer Join Date: May 2005
Posts: 933
| The template class uses function pointers, therefore you can access the functions of the derived classes... Code: class WalkForwardCommand : public Command
{
public:
WalkForwardCommand() {}
virtual ~WalkForwardCommand() {}
virtual void Execute()
{
std::cout << "\nWalking forward...\n" << std::endl;
}
};
Oh wait, command has a virtual Execute function, let me think on this one... I think this is where you're asking too much Dae. (someone correct me if I am wrong) What needs to access which functions in your classes? A render function needs to access model data, therefore you create a Scene_Object class, which handles nothing but pictures. A Game world needs to access physics and collision detection, therefore you create a Physics_Object class, the reason you can't combine Scene_Objects and Physics_Object is because the renderer doesn't need to know physics data and functions, it just needs to know a final result (x, y, z changes) Having one class that does it all, simply isnt the essense of OO.. Think, A Goblin doesn't have physics, physics effect Goblins. You, unfortunately Dae, cannot create a super Object class unless you use 100 virtual functions for everything any object might use, even if they don't use it... (correct me if I am wrong, someone) I might be talking way over my head of knowledge, but you would think if you've been searching for any amount of time at all, and no one could answer your question efficiently, it simply can't happen, I've seen your posts, they make perfect sense... I'm going to email the creator a few times and ask him questions. Code: class OgreMonster : public Monster
{
public:
OgreMonster(float x, float y, float z)
{
std::cout << "Ogre monster created at coordinate: " << x << ", " << y << ", " << z << std::endl;
}
};
OR: You could create an OgreMonster factory, with a couple functions specific to OgreMonster... I think that would become redundant, because that would be narrowing down our thing we wanna return, we wanna be able to return a pointer to a general monster while keeping all that data... Hmmm FINALLY: I think I have the answer, its function pointers, simple as that, easy as pie. Instead of having the Constructor of OgreMonster have just an x,y,z arguement, give it more arguements, one pointer for each function. This way those function pointers would be created in the new Monster object, reay to use. (right? )
__________________ Sometimes I forget what I am doing when I enter a room, actually, quite often. Last edited by Shamino; 12-16-2005 at 10:11 PM. |
| Shamino is offline | |
| | #4 | |||||||
| Deprecated Join Date: Oct 2004 Location: Canada
Posts: 1,032
| No, your monster1/2 would not be able to access Ogre/Goblin specific functions because you're casting them to the base class Monster. Quote:
I reread the article and completely understand now. It is not meant to resolve my or your issue. I was actually wrong and it doesn't even store the instances. However it does create them and then return them, so thats why your constructor for Ogre/Goblin was being called. I believe he simply casted the returned class to the base class as an example, which would be file for interfaces. Rereading the intro in that article I can't believe I spent so much time looking at it. I've decided to stop reading these specific design patterns for a while, at least until I finish the prereq Design Patterns. Quote:
Sorry if I was the one that got you hung up on my goal to make it so there was no dataloss when recieving an Object as a parameter. I've decided I've probably been overdoing it and yeah, expecting too much. So I've decided to simply put all information (mass, velocity, coords, etc) that CPhysics, CWorld, or CRender might need for Object's in Object. Quote:
Quote:
Quote:
Quote:
Quote:
__________________ Warning: Have doubt in anything I post. GCC 4.5, Boost 1.40, Code::Blocks 8.02, Ubuntu 9.10 010001000110000101100101 Last edited by Dae; 12-17-2005 at 02:07 AM. | |||||||
| Dae is offline | |
| | #5 |
| Absent Minded Programmer Join Date: May 2005
Posts: 933
| The only arguement I have is about Goblin Knowing its mass and velocity, Goblin doesn't need to know those things, some other higher power does, (a physics engine).. But for the most part you are right. Although, You mean we could solve the problem with function pointers in the constructors parameters, so they would be passed to the template class? If theyre the devil, they sure allow you to do a beautiful thing.
__________________ Sometimes I forget what I am doing when I enter a room, actually, quite often. |
| Shamino is offline | |
| | #6 | ||
| Absent Minded Programmer Join Date: May 2005
Posts: 933
| Few words from Robert Geiman Quote:
Quote:
__________________ Sometimes I forget what I am doing when I enter a room, actually, quite often. Last edited by Shamino; 12-17-2005 at 04:31 PM. | ||
| Shamino is offline | |
| | #7 |
| Deprecated Join Date: Oct 2004 Location: Canada
Posts: 1,032
| Hmm.. Well his suggestion is the best way to accomplish it, but like you said thats what we are trying to avoid - virtual functions. The thing is that function pointers could possibly work (I dont have much experience with them, especially in a case where you're using like 10 of them), but that would kind of be defeating the point since all we are trying to avoid is 1 vtable lookup. I forgot to mention that EFnet also said that the extra vtable lookup isn't liable to be a problem unless you're planning on performing it thousands or millions of times a second (like with vectors or vertex operations). I do understand these concepts more than I did before, but not entirely because of course they are all new to me. His article even refers you to read other articles first. If you check one of the other articles its more clear on the purpose of a pluggable factory. The point of it is to take an id and call a class based on that. Which makes complete sense when you think about say networked games. You send an id to a client and the client knows which class to call based on what id is sent. Then what his article is meant to accomplish is a generic ability to use it for any base classes, with the ability to choose which derived class is chosen at run-time, while also being quite quick because it uses a map. Honestly theres no purpose for us to use this article because we already have access and know which classes we want to call. The only problem was that we don't know which class we want to accept in say CRender, so we use Object*. Then the problem is that when casting a subclass of Object that has data that isn't from Object (and Object isnt concrete (virtual functions)) we lose it. The thing is even though thats the problem, I cant think of ANY case that CRender, etc. will be accessing data that not all Object's share (velocity, mass, etc.) - I was just thinking ahead. Even if I can find a case where this becomes a problem, I could simply overload the function to accept that subclass, or if I dare use "the devil" templated methods. Problem solved. For me the problem never really existed, because as I said was just thinking ahead. For you though I guess it does, you don't seem to want to give Object this data. Goblin may not need to know what his mass is, thats an opinion, but I don't agree entirely. I can think of methods that Goblin would have where it needs to know its mass, and even if it doesn't need to know its mass, it needs to store that data because its a property of itself and must send that data to CPhysics. You could use templated methods to solve your problem (which is like many overloaded methods for each different subclass of Object). I'm pretty clear on what I'm doing now, but good lucky to you in your search.
__________________ Warning: Have doubt in anything I post. GCC 4.5, Boost 1.40, Code::Blocks 8.02, Ubuntu 9.10 010001000110000101100101 |
| Dae is offline | |
| | #8 | |
| Cat without Hat Join Date: Apr 2003
Posts: 8,492
| That's quite simply not possible in the static typing system of C++. In order to call a function on an object, you need to have a reference or pointer to that object that is of a type that actually contains the function. Therefore, to call an OgreMonster-specific function, you need an OgreMonster* to the object. If the object is currently only available as a Monster*, you have to do a downcast, which means you also need to find out if the runtime object is of type OgreMonster (or derived). This is possible using dynamic_cast or some other RTTI mechanism, but it always involes an if(). You have to see it this way. Given an object factory unspecified create(Tag type); and a variable Tag tag; the call Monster *p = create(tag); is for the compiler completely opaque (usually). tag might be OGRE_TAG or GARGOYLE_TAG, but there's not way to know that at compile time. Who knows, the value of tag could have been created like this: tag = rand() % 2 ? OGRE_TAG : GARGOYLE_TAG; The best you can supply for 'unspecified' therefore is Monster*, meaning you lose some type information. On the other hand, one of Dae's basic requirements in this debate is flawed: Quote:
There's another interesting topic here, and that's where you draw the line of additional subclasses. Take Blizzard's Diablo 2, for example. (It's a good example, because I know for a fact that it's written in C++.) Do you think they wrote a Skeleton class, a Ghoul class, a Sabercat class, and an Andariel class? Or to go further, did they write classes for BurningDead, Horror, Returned and all the other classes of skeletons you encounter? They did not. There might be a Monster class deriving from Character, though I wouldn't even bet on that, and that Monster class is then configured solely by properties: which monster type it is, which model therefore is to be used, what class it is, what level, what special properties it has. There's behaviour scripts attached, and action scripts (e.g. those nasty oversized bugs that cast lightning every time they're hit). All that comes from data files - even the scripts. It's called data-driven design. I'm just mentioning this because separate classes for Ogres and Goblins seem too specific for me. But it's also a question of extensibility vs effort. It's easier to write a few specific classes than to write the environment that allows extensibility a la Total Annihilation.
__________________ All the buzzt! CornedBee"There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code." - Flon's Law | |
| CornedBee is offline | |
| | #9 |
| Absent Minded Programmer Join Date: May 2005
Posts: 933
| Well, I guess this is where our problems begin to make a fork in the road. If I could get his class factory to pass function pointers to the templated class, and then use them accordingly, I'm done, my quest is over, and I'm finding that one out now. Good luck to you as well Dae .
__________________ Sometimes I forget what I am doing when I enter a room, actually, quite often. |
| Shamino is offline | |
| | #10 |
| Absent Minded Programmer Join Date: May 2005
Posts: 933
| CornedBee You're saying that we cant have Base class (templated) A = to a Derivedren B, C, D, or E? We want to be able to pass a single *Object to a function, rather than a B, or a C, or a D, or an E... This = impossible? ![]() Honestly, if they'd just give us the ability to recompile on runtime I'd be happy LOL.
__________________ Sometimes I forget what I am doing when I enter a room, actually, quite often. Last edited by Shamino; 12-17-2005 at 05:08 PM. |
| Shamino is offline | |
| | #11 | ||
| Deprecated Join Date: Oct 2004 Location: Canada
Posts: 1,032
| Quote:
BTW that was a typo, I meant: Quote:
Would you mind giving us an example of your ideal Object and derived classes so we can see why exactly you're avoiding putting velocity, mass, etc. in Object so much? Thats what EFnet and Gamedev recommended to do. I don't see any problem with that, the only reason I brought it up was because I was thinking ahead. Oh yeah I forgot, thanks for the reminder CornedBee, I was planning on splitting it up into something like PhysicalObject, Renderable, nonRenderable, etc. as I read half an article about this months ago. Yeah that is an interesting topic, I wouldnt mind reading up on how to do that, haha.
__________________ Warning: Have doubt in anything I post. GCC 4.5, Boost 1.40, Code::Blocks 8.02, Ubuntu 9.10 010001000110000101100101 | ||
| Dae is offline | |
| | #12 | |||
| Absent Minded Programmer Join Date: May 2005
Posts: 933
| Quote:
Quote:
Also, in case you might be misunderstanding me, I would like to have OgreMonster control all of its own data, I don't want to split it up into a million classes, I <DO> want OgreMonster to have its own functions, its own data, so that everything can use OgreMonster correctly, I want the renderengine, the physics engine, the sound engine, everything, to be able to use OgreMonster as a client.... I ultimately wanna be able to do this (pulls up his project) Code: class Object
{
public:
bool MarkedForDeletion;
struct Location
{
float locx;
float locy;
float locz;
};
};
class Cross : public Object
{
public:
MS3DModel *Model1;
};
Then Ultimately.. Code: if (Obj != NULL)
{
Scene_Object_Manager::AddScene_Object(Obj);
}
Then after that we'll have a draw function in the render class simply file through that container of scene objects, grab their model data, and draw it accordingly... Robert Geiman told me this Quote:
__________________ Sometimes I forget what I am doing when I enter a room, actually, quite often. Last edited by Shamino; 12-17-2005 at 05:45 PM. | |||
| Shamino is offline | |
| | #13 | ||
| Deprecated Join Date: Oct 2004 Location: Canada
Posts: 1,032
| Then what did this mean? It implies you dont want Goblin to know its own information. Quote:
I think I get what you mean in your last post (even though your previous posts go against it), that you want the data to be in Goblin and not in Object? Is that what you mean? It couldnt be, because Goblin derives from Object and therefor has that data. Your code works fine, just put MS3DModel *Model1; in Object, I mean what Object's couldn't be Rendered anyway? If there are some you can think of, just use CornedBee's advice and make a class RenderableObject and put MS3DModel *Model1; in that, and derive Goblin from that, and pass Goblin to CRender, which has parameter RenderableObject*. I don't see you having any problem doing that Shamino. Quote:
__________________ Warning: Have doubt in anything I post. GCC 4.5, Boost 1.40, Code::Blocks 8.02, Ubuntu 9.10 010001000110000101100101 | ||
| Dae is offline | |
| | #14 | |
| Deprecated Join Date: Oct 2004 Location: Canada
Posts: 1,032
| Quote:
Only one vtable lookup (which I've said two times now isn't a problem unless you're calling it many times a second) thats pretty powerful. I don't think I'll worry too much about virtual functions for my Objects. Especially when its only a few.
__________________ Warning: Have doubt in anything I post. GCC 4.5, Boost 1.40, Code::Blocks 8.02, Ubuntu 9.10 010001000110000101100101 | |
| Dae is offline | |
| | #15 |
| Absent Minded Programmer Join Date: May 2005
Posts: 933
| I contradicted myself in hopes of having his object factory be my answer, lol.... All I want is simple, but no one is saying how to do it.... I want to have a base Monster class I want to have lots of little monster derivedrens... I want to pass a MONSTER to my push_back call.. I want to draw what is in that vector! I want what is in the vector to have all the little functions that the monster derivedrens have, therefore everything on the scene can do wtf they wanna do, and are created to do, (see monster derivedrens) 5 steps to uber object management.... I could pass the derivedrens of Monster to the vector instead, but then I'd have to add a case statement for every single monster, and ultimately have a 100 line long switch, figuring out which Monster derivedren to add to the vector.... (there must be a better way).... This would solve my problem, but also be clunky and become long and unmanagable...
__________________ Sometimes I forget what I am doing when I enter a room, actually, quite often. |
| Shamino is offline | |
![]() |
| Thread Tools | |
| Display Modes | |
|
Similar Threads | ||||
| Thread | Thread Starter | Forum | Replies | Last Post |
| using this as synchronization object | George2 | C# Programming | 0 | 03-22-2008 07:49 AM |
| problem with answer going to default? | Patrick1234 | C++ Programming | 4 | 10-02-2002 09:11 AM |
| slight problem just can't see the answer | anthonye | C++ Programming | 1 | 07-05-2002 08:45 AM |
| I've ran into a problem | Mike Jones | C Programming | 1 | 03-27-2002 04:08 PM |
| code help :) | Unregistered | C Programming | 4 | 02-28-2002 01:12 PM |