|
||||||||||||||||||
Add Forum to Favorites | Send Topic To a Friend | View Forum FAQ | Track this topic Page: 1 2 »» |
Last Thread Next Thread ![]() |
| Improving Performance in C++ with Compile Time Polymorphism |
|
![]() TempusElf Member since: 3/23/2002 From: Chicago, IL, United States |
||||
|
|
||||
| Good Idea... I don't use polymorphism very often so I never really thought about this kind of overhead... now I know how to look out for it... I'm still having trouble understanding the "Curiously Recurring Template Pattern" So I can do something like this?:
template<typename GenType>
class Character
{
/* class stuff... no reference to GenType... or did I miss that?*/
};
class NPC: public Character<NPC>
{
};
I'm having trouble getting my head around this... It looks to me like NPC is inheriting from itself in a way... because NPC makes up the type of Character which makes up the type of NPC with makes up the type of Character which.... Obviously I'm missing something fundamental [edited by - tempuself on November 7, 2003 1:11:04 PM] |
||||
|
||||
![]() Ready4Dis Member since: 4/26/2002 From: USA |
||||
|
|
||||
| Templating isn't inheriting. Think of it this way..
#define GenType NPC
class Character
{
GenType *SomeVar;
void DoSomething(GenType param);
};
class NPC : public Character
{
};
That's the same as doing this:
template <class GenType>
class Character
{
public:
GenType *SomeVar;
void DoSomething(GenType param);
};
class NPC : public Character<NPC>
{
};
It's simply telling it what to fill in for the type, so in essence, all it's doing is this:
class NPC
{
NPC *SomeVar;
void DoSomething(GenType param);
};
Now, this is legal as long as you don't declare a normal variable, only a pointer and/or parameters. If you try declaring it as a normal variable (GenType SomeVar |
||||
|
||||
![]() TempusElf Member since: 3/23/2002 From: Chicago, IL, United States |
||||
|
|
||||
| Cool... I think I get it now |
||||
|
||||
![]() GMMigge Member since: 8/26/2002 From: Landskrona, Sweden |
||||
|
|
||||
| Hehe, sometimes those smileys don't help you a bit. Like in when where you wrote: " (GenType SomeVar |
||||
|
||||
![]() Sneftel Moderator - Consoles, PDAs, and Cell Phones Member since: 7/7/2001 From: Philadelphia, PA, United States |
||||
|
|
||||
| Yeah... I hate accidentally referring to std: "Sneftel is correct, if rather vulgar." --Flarelocke |
||||
|
||||
![]() duke Member since: 7/27/2002 From: USA |
||||
|
||||
| The author is mis-using reinterpret_cast in this article. The correct method is to use static_cast for all the examples he gave. Other than that it is a good article on template programming but the specific examples are a bit contrived, but I suppose they have to be in order to get the point across without putting up an entire program. |
||||
|
||||
![]() jamessharpe Member since: 1/6/2003 From: Bristol, United Kingdom |
||||
|
|
||||
| What about the case where you wanted say a vector of Shape objects, that could be any class derived from shape, and wanted to store them as a queue for later processing in a function? Is this a limitation of this system? |
||||
|
||||
![]() Lepton Member since: 11/2/2003 |
||||
|
|
||||
quote: dynamic_cast would be more suitable for casting down inheritence heirarchies. Few comments about the article, its quite well written, dont see the need for all the implementation details in it though, somewhat takes away from the point of the article IMHO. The main problem i have with this article is that there is nothing original in it, static polymorphism is discussed in many text books, using the exact same shape examples. Would be nice to see some original thinking and ideas in a gamedev article for once. I also note that there is no list of sources at the bottom of the article, and im in no doubt that he used sources for this article, is there a reason for this? |
||||
|
||||
![]() Sneftel Moderator - Consoles, PDAs, and Cell Phones Member since: 7/7/2001 From: Philadelphia, PA, United States |
||||
|
|
||||
quote: True. Mea culpa. I'd point out, however, that the only time where it would make a substantive difference is when a class uses multiple inheritance. quote: When I originally conceived the article, it was a case study of how I'd used methods such as these to improve my content loading code. But since that goes into a lot of disparate topics, I figured I'd focus it down. Perhaps sometime I'll write another article covering it more fully. "Sneftel is correct, if rather vulgar." --Flarelocke |
||||
|
||||
![]() neurokaotix Member since: 7/2/2002 |
||||
|
|
||||
| Nice article Sneftel, I'm anxious to go evaluate my engine code to see where this might come handy. I'm using a good deal of virtual functions :/ James Simmons MindEngine Development http://medev.sourceforge.net |
||||
|
||||
![]() ZealousElixir Member since: 6/20/2001 From: Huntsville, USA |
||||
|
|
||||
quote: You can always write the definition in an INL (or an excluded-from-build CPP) file, and include that in the header. Makes things just about as tidy. |
||||
|
||||
![]() superpig GDNet Technical Lead Member since: 5/26/2001 From: Oxford, United Kingdom |
||||
|
|
||||
| Hmm... I wonder if this could speed up the Enginuity memory manager. One limitation that you hinted at, Sneftel, but didn't outright name, was that it becomes impossible to aggregate Shapes like you might want to. For example, I'm writing some physics code at the moment which deals with a number of varying collision volumes (spheres, boxes, cylinders, and so on) which all inherit from CPhysicsPrimitive. I can then store a list of CPhysicsPrimitives, and iterate through it to update all my objects. If CPhysicsPrimitive became parametrized by my shape classes - CPhysicsPrimitive<CSphere> and so on - then I lose my ability to do that. One solution would be to derive the templated base class itself from *another* base class which is non-parametrised - CPhysicsEntity. I could then store a list of CPhysicsEntity* with no problem. However, to get at the actual object I then need some virtual functions... which defeats the point of the system in the first place I guess for a fully flexible system it's impossible to avoid virtual functions completely. But curiously recurring templates could still be useful - if I were to perform a series of operations on a single shape, I could use a virtual function to get an ID number for the underlying type, and cast it to BaseClass<thatType> (essentially my own RTTI), and then operate on it that way, avoiding the virtual functions... but then I guess if you're going to do that, you might just as well cast to 'thatType' itself and skip the base class. Without the templated operational functions (your DrawAShapeOverAndOver) I can't see it fixing very much... though it could be useful for the Enginuity size() function. I'd be able to remove that function from the vtbl and just do 'long size(){return sizeof(T)}' where T is the parameter. Richard "Superpig" Fine - saving pigs from untimely fates, and when he's not doing that, runs The Binary Refinery. Enginuity1 | Enginuity2 | Enginuity3 | Enginuity4 | Enginuity5 ry. .ibu cy. .y'ybu. .abu ry. dy. "sy. .ubu py. .ebu ry. py. .ibu gy." fy. .ibu ny. .ebu "Don't document your code; code your documentation." -me |
||||
|
||||
![]() Sneftel Moderator - Consoles, PDAs, and Cell Phones Member since: 7/7/2001 From: Philadelphia, PA, United States |
||||
|
|
||||
quote: Yeah, I've explored that system somewhat. It looks something like this:
class A {
virtual void Foo_Aux();
void Foo() { Foo_Aux(); }
};
template<typename AType>
class A_Aux : public A
{
void Foo() {
static_cast<AType &>(*this).Bar();
static_cast<AType &>(*this).Baz();
}
virtual void Foo_Aux() { Foo(); }
};
class B : public A_Aux<B>
{
void Bar() { ... }
void Baz() { ... }
};That way, you can have an A* and call Foo on it, or you can have an AType as a template argument. But no matter how I've tried to format it, it's been unwieldy, ugly, and hackish. quote: What would be really nice would be a way to force a particular virtual function binding (a 'final' keyword would be ideal), instead of allowing the compiler to do a lookup. But AFAIK such a thing is not to be found in C++. Perhaps Stupid Macro Tricks, but I hate that. "Sneftel is correct, if rather vulgar." --Flarelocke [edited by - sneftel on November 7, 2003 10:31:09 PM] [edited by - sneftel on November 7, 2003 10:31:47 PM] |
||||
|
||||
![]() IFooBar Member since: 4/17/2002 From: manama, Bahrain |
||||
|
|
||||
quote: You never know with sneftel. It's very probable that he came up with this stuff all on his own, being the C++ Hotty that hs is Very informative article sneftel. easy to read and understand. [edit] quote tag and smiley[/edit] :::: [ Triple Buffer ] :::: [edited by - ifoobar on November 7, 2003 10:52:58 PM] |
||||
|
||||
![]() aboeing Member since: 1/11/2003 From: Perth, Australia |
||||
|
|
||||
| Kool article, was waiting a while for this one Just trying to think of a way to overcome the problems, can someone tell me why this doesnt work:
class VirtualShape {
public:
virtual void VirtualDraw() = 0;
};
template <typename ShapeType>
class Shape : public VirtualShape {
public:
void Draw() const {
static_cast<const ShapeType *>(this)->Draw();
};
};
class Rectangle : public Shape<Rectangle> {
public:
void Draw() {
printf("drawarect\n");
}
virtual void VirtualDraw() {Rectangle::Draw();};
};
class Circle : public Shape<Circle> {
public:
void Draw() {
printf("drawacircle\n");
}
virtual void VirtualDraw() {Circle::Draw();};
};
template<typename ShapeType>
void DrawAShapeOverAndOver(ShapeType* myShape)
{
for(int i=0; i<5; i++)
{
myShape->Draw();
}
}
void DrawAChangingShapeOverAndOver(VirtualShape **myShape) {
for(int i=0; i<5; i++)
{
myShape[i]->VirtualDraw();
}
}
It always calls circles draw method,... (ie: rectangle->VirtualDraw(); calls circles draw..) Thanks! |
||||
|
||||
![]() Captian Goatse Member since: 8/16/2003 |
||||
|
|
||||
| This idea somehow perverts the initial idea of polymorphism and object structure. First we may ask these questions: Does a picture draw itself? picture.draw(); No. Does artist draw the picture? artist.draw(picture); Yes. In this case we can still use the same method, however if you are going to be strict about polymorphism, this is the "more" right way. Of course we are all humans and in the end it comes down to matter of preferance. Also, putting your drawing headers into one file improves the compile time performace on some compilers. [edited by - Captian Goatse on November 8, 2003 3:25:52 AM] |
||||
|
||||
![]() Seriema Member since: 6/15/2001 From: Stockholm, Sweden |
||||
|
|
||||
| Nice article! I just got hoocked up on one thing... What's the point? SpecificType *mySpecificObjectPtr = new SpecificType(); mySpecificObjectPtr->CallAnyFunctionThatSpecificTypeHas(); that's basicly what I saw... You don't need virtual functions. You can inherit or not, either way, all functions that SpecificType has will be called without vtbl overhead. Because, as it said in the article, you can't have a base class pointer so you just lost the whole run-time dynamic binding part. Dunno, seems like an awful long way to go to achieve what seems to be my example above? Either way, it's always nice to see new ideas }-- Programmer/Gamer/Dreamer --{ |
||||
|
||||
![]() Sneftel Moderator - Consoles, PDAs, and Cell Phones Member since: 7/7/2001 From: Philadelphia, PA, United States |
||||
|
|
||||
quote: That's true. But this only allows you to refer to the particular hard-coded type in that block. Where this technique really starts becoming useful is when an object is being used in an external polymorphic situation, such as DrawAShapeOverAndOver() did. "Sneftel is correct, if rather vulgar." --Flarelocke |
||||
|
||||
![]() liquiddark GDNet+ Member since: 4/14/2001 From: Kitchener, Canada |
||||
|
|
||||
| In specific reference to the article's technical thrust, isn't it a bad idea for an object to draw itself anyway? Shouldn't the engine be responsible for this? ld |
||||
|
||||
![]() Cedric Kerr Member since: 10/25/2003 |
||||
|
|
||||
quote: I disagree with this. Having each object being responsible for rendering itself is more inline with polymorphism. With the second approach the renderer has to know how to draw each type of object making it more hassle to add new objects to the system as you have to keep updating the rendering routine for each time and that's assuming you even have the privileges to change it. With the first approach you simply write each new class which implements the base interface and your renderer will automatically be able to handle them without having to know the specific details of what it's drawing, which one of the main ideals of polymorphism. Although with this example I'd also be inclined to pass the renderer in as a parameter for added flexibility. -- "Never Abandon Imagination" - Tony DiTerlizzi |
||||
|
||||
![]() Sneftel Moderator - Consoles, PDAs, and Cell Phones Member since: 7/7/2001 From: Philadelphia, PA, United States |
||||
|
|
||||
| If you want to get all Object-Oriented about it, a Bridge Pattern is probably the most robust and flexible design. I've done a little thinking about how to do a compile-time Bridge Pattern, but haven't come up with an elegant solution yet. "Sneftel is correct, if rather vulgar." --Flarelocke |
||||
|
||||
![]() Beer Hunter Member since: 11/22/2000 From: Canberra |
||||
|
|
||||
quote:Right. However, you can specifically call Rectangle::DrawOutline like this:
void DrawAShapeWhichIsARectangleOverAndOver(Shape* myShape)
{
for(int i=0; i<10000; i++)
{
reinterpret_cast<Rectangle*>(myShape)->Rectangle::DrawOutline();
}
} |
||||
|
||||
![]() Sneftel Moderator - Consoles, PDAs, and Cell Phones Member since: 7/7/2001 From: Philadelphia, PA, United States |
||||
|
|
||||
quote: Absolutely right. And, of course, what that means is that compile-time polymorphism can be implemented without touching the class at all, if you aren't using things like the Strategy Pattern. I think this approach is a bit messy, because it puts the burden of potentially unsafe downcasting outside the class (IMHO, fugly things like casts should be discreetly hidden in source files). Still, if you don't feel like screwing with the CRTP, this can be a good substitute in places. "Sneftel is correct, if rather vulgar." --Flarelocke |
||||
|
||||
![]() brain21 Member since: 11/19/2001 From: Canada |
||||
|
|
||||
I'm new to C++, but I was wondering couldn't you just do this?:
void DrawAShapeOverAndOver(Shape* myShape)
{
void (*Draw)();
Draw = myShape->DrawOutline;
for(int i=0; i<10000; i++)
{
Draw();
}
}
Just save the function then then call that pointer?? Denny. [edited by - brain21 on November 9, 2003 7:56:55 PM] |
||||
|
||||
|
Page: 1 2 »» All times are ET (US) ![]() |
Last Thread Next Thread ![]() |
|