Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411430 Posts in 69363 Topics- by 58416 Members - Latest Member: JamesAGreen

April 19, 2024, 10:40:05 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)RTTI pros and cons
Pages: [1]
Print
Author Topic: RTTI pros and cons  (Read 5639 times)
Will Vale
Level 4
****



View Profile WWW
« on: March 10, 2009, 12:58:30 PM »

This came up in Alec's Monocle thread and to avoid derailing that I thought it was worth moving the discussion here.

Basically, I've had good experience using reflection/RTTI (in my case, home-made rather than inbuilt) in C++ projects, but I know others haven't. I'm interested to hear what the consensus is, find out if I'm missing some big gotchas, or suggest solutions to problems other people are experiencing. I'd add a poll but I don't know how Sad

If there's technical interest I can outline how my RTTI stuff works as well, but that's going to be a longish post.

Cheers,

Will
Logged
Ivan
Owl Country
Level 10
*


alright, let's see what we can see


View Profile
« Reply #1 on: March 10, 2009, 01:09:44 PM »

I am personally against such things and believe that a well designed system has no need for it. Besides, from what I understand dynamic_cast in C++ is slow.
Logged

http://polycode.org/ - Free, cross-platform, open-source engine.
David Pittman
Level 2
**


MAEK GAEM


View Profile WWW
« Reply #2 on: March 10, 2009, 01:16:46 PM »

I've also rolled my own, and other than the time investment of doing that (which isn't even especially significant), I can't imagine any arguments against it (except that you can't dynamic_cast, I guess; but you can implement your own equivalent). Some might argue that a reliance on RTTI is indicative of poor architecture (e.g., querying the class to determine some behavior, versus implementing the behavior in virtual functions), but that's a separate issue. I actually use my own RTTI very rarely for that reason.

Built-in RTTI uses strcmps for its type comparisons (and O(n) of them for an inheritance hierarchy of depth n, as I recall), at least compiling with MSVC. That sounds pretty bad, especially if you overuse type comparisons. On the other hand, I did some HL2 modding a while back and saw dynamic_casts used pretty freely. If it's good enough for Valve... Shrug
Logged

Will Vale
Level 4
****



View Profile WWW
« Reply #3 on: March 10, 2009, 01:39:29 PM »

I'm not a fan of dynamic_cast either, I have an equivalent but don't use it in my stuff - as you say, a good system with e.g. interfaces + implementations doesn't usually need to downcast. I do like the idea of an rtti::StaticCast<T> which does a dynamic cast in debug builds to assert that the static cast is valid.

The question that I always answer with RTTI is "how do I convert a string to a type". For example, if a config file says that an Entity has a Pilot component, how do you create that component without string-to-type of some kind? If you e.g. have a dictionary of factory functions, that's a form of reflection already, although it doesn't have a direct type_info equivalent.
Logged
Mikademus
Level 10
*****


The Magical Owl


View Profile
« Reply #4 on: March 10, 2009, 01:52:18 PM »

I am personally against such things and believe that a well designed system has no need for it. Besides, from what I understand dynamic_cast in C++ is slow.

Generally true, there are very few times RTTI is really needed, and when using polymorphism you only really need dynamic_cast when downcasting in multi-inherited objects. However, RTTI can be an extremely useful tool and dynamic_casting isn't that slow. If you don't do it inside inner loops and crazy shit like that it's all good, given some common sense.
Logged

\\\"There\\\'s a tendency among the press to attribute the creation of a game to a single person,\\\" says Warren Spector, creator of Thief and Deus Ex. --IGN<br />My compilation of game engines for indies
Snakey
Level 2
**


View Profile WWW
« Reply #5 on: March 11, 2009, 12:08:25 AM »

For RTTI you would generally need some sort of dictionary stored somewhere. When you need to add new type data you have to adjust this yourself. What I find the problem is, is what you use for the type information. If you just use a number, then it can be problematic. For example:

int GetRTTI() const
{
  return 0;
}

0 could be an enum or whatever else, but if you are just returning an int what is the likely risk in which another class unknown to you may also return a zero ... and once that happens you'll have problems which might require a bit of head scratching. Since most indie developers just work on their own, this isn't such a large problem but certainly it can be when projects become much larger scale.

Currently, I prefer to use strong interface designs in which the base class tend to have very low level generalized methods to allow high level data manipulation in the subclasses. The only minor problem with this is that the vtable can consume quite a bit of memory althouugh this is generally less than 100 bytes. So for a standard gaming rig, which tend to have more than half a gig of ram it really doesn't matter.

Lastly, this whole thing of converting a string into class ... this almost sounds like you kind of want to investigate into scripting languages. I mean, you could do it ... but it seems like you may want to use C++ in a way it wasn't really designed to be used in.
Logged

I like turtles.
bateleur
Level 10
*****



View Profile
« Reply #6 on: March 11, 2009, 04:44:20 AM »

Excuse me asking a hopelessly basic question, but what is RTTI actually for?

I'm finding it hard to imagine a situation where I'd want to cast something to a type not known at compile time. Because the code immediately following the cast is presumably written to expect various fixed types anyway.

I suppose multiple inheritance is an exception because of horrendous problems with "this" (I never use C++ though, so I'm only saying that based on other people's horror stories). Are there any single inheritance cases that actually achieve something worthwhile?
Logged

lithander
Level 3
***


View Profile WWW
« Reply #7 on: March 11, 2009, 06:39:56 AM »

My take on RTTI is that it's great if properly integrated in your programming language and should be avoided if it's not. In C++ we have maybe 10 dynamic_cast in the hundred-thousands of lines of gamecode. In C# however, reflection a great way to setup easily maintainable and extendable projects.
Logged

David Pittman
Level 2
**


MAEK GAEM


View Profile WWW
« Reply #8 on: March 11, 2009, 08:10:40 AM »

If you just use a number, then it can be problematic. For example:

Simply enumerating types also isn't sufficient because it doesn't handle inheritance at all. (To be fair, C++'s typeid/type_info doesn't supply inheritance information either, but it doesn't need to because dynamic_cast handles that under the hood. But if you disable C++ RTTI entirely and roll your own, you can't dynamic_cast, so your custom RTTI should have an equivalent function.)

Excuse me asking a hopelessly basic question, but what is RTTI actually for?

I'm finding it hard to imagine a situation where I'd want to cast something to a type not known at compile time. Because the code immediately following the cast is presumably written to expect various fixed types anyway.

I think that's exactly why dynamic_cast and typeid are so rarely seen in C++ projects. The only case I ever see it used is when a function needs to handle one particular class as a special case and it's simpler to implement this way than defining some top-level virtual function that does nothing except for that class. E.g.,

Code:
void Foo( Bar* pBar )
{
    // Do something to pBar, then...
    // Handle the special case where pBar is a Bletch
    Bletch* pBletch = dynamic_cast< Bletch* >( pBar );
    if( pBletch )
    {
        // Handle the Bletch
    }
}

Which is arguably better encapsulated and cleaner than mucking up your interfaces like so:

Code:
class Bar
{
    // Snip
    virtual void HandleBletch() {}
}

class Bletch : public Bar
{
    // Snip
    virtual void HandleBletch() { /* Functionality!! */ }
}

void Foo( Bar* pBar )
{
    // Do something to pBar, then...
    // Handle the special case where pBar is a Bletch via virtual functions
    pBar->HandleBletch();
}
« Last Edit: March 11, 2009, 08:25:20 AM by David Pittman » Logged

Mikademus
Level 10
*****


The Magical Owl


View Profile
« Reply #9 on: March 11, 2009, 08:22:48 AM »

Excuse me asking a hopelessly basic question, but what is RTTI actually for?

Dynamic polymorphism. If all you use is static polymorphism then you'll never need RTTI.
Logged

\\\"There\\\'s a tendency among the press to attribute the creation of a game to a single person,\\\" says Warren Spector, creator of Thief and Deus Ex. --IGN<br />My compilation of game engines for indies
Snakey
Level 2
**


View Profile WWW
« Reply #10 on: March 11, 2009, 11:04:01 AM »

Quote
David Pittman

I agree. During the planning stages, I usually always make sure that the base classes don't have some random virtual function that is only ever relevant to a specific sub class down the line as that defeats the the purpose of base interface anyways!
Logged

I like turtles.
Will Vale
Level 4
****



View Profile WWW
« Reply #11 on: March 11, 2009, 03:20:10 PM »

There seems to be a strong identification of RTTI with dynamic_cast/dynamic polymorphism, which is probably because that's an obvious feature provided by C++ inbuilt RTTI. Maybe I should have said reflection - although they both contain the idea of finding out 'stuff' about types at runtime.

It is absolutely like adding a language feature, and I agree that there are probably languages other than C++ which would allow me to do the same things more naturally, but then if I want to ship a commercial game it (almost - there's the XNA/C# option) has to be C++.

The things I currently use RTTI for:

* Looking up types from typenames. For example, creating a facet for an entity based on a definition file.

Code:
const rtti::IType* facet_type = rtti::IType::Find( Hash(type_name) );

* Lightweight verification and access to facts about runtime types (like type traits but runtime not compile time).

Code:
SIL_ASSERT( facet_type->IsA( rtti::Type<IFacet>() ) );
SIL_ASSERT( facet_type->IsConcrete(); );

* "Anonymous" construction. Creating the facet is now easy:

Code:
// This does the verification above internally, then allocates 
// facet_type->Size() bytes and constructs an instance of the facet.
IFacet *facet = facet_type->New<IFacet>();

* Implementation of variant types - good for scripting, settings, properties, etc.

* "Anonymous" access to members. After creating the facets for an entity, and connecting them to the entity, a function can walk the members looking for "pointer to facet type" instances and connecting them to the relevant facet in that entity. Variants are used here as well to make the interface typesafe.

In the past I've also used RTTI for savegames and automatic serialisation - again via walking members.

None of this stuff is impossible without RTTI, but you have to write more code for it, and that kind of code can be repetitive and therefore error prone - I hate repetition more than anything else, so that's a huge plus for me.

Cheers,

Will

[edit] Typo.
« Last Edit: March 11, 2009, 04:15:30 PM by Will Vale » Logged
Snakey
Level 2
**


View Profile WWW
« Reply #12 on: March 11, 2009, 04:42:21 PM »

The main thing that irks me about RTTI is that I usually follow the paradigm that I shouldn't really have to ask an instance or a class what it is. C++ already know what it is.

However, you do present RTTI in an interesting way in that you are using a script to then look up class within your C++ code. However, I'd say to just use a scripting language which can handle classes by themselves such as Squirrel.

At the end of the day it comes down to flexibility and error prone. If it's possible for a class to report the same RTTI data as another class then there is room for problems. Is RTTI really flexible in the system you've designed or is it a perceived flexibility (Having to update or maintain anything whether it is in the class or otherwise is not adding to flexibility). The ultimate test to that is, if you change your RTTI or change the way RTTI is represented, how much other code is affected (coupling).

I stress however, that this is my personal view and opinion. I mean I can see why some people use RTTI. I suppose the offset about my method is that it uses more memory and it can be slower (due to virtual table traversing), however I find that my methodology is rather agile and most errors I get are picked up by the compiler rather than at runtime.

There quite a few design patterns I don't like. RTTI is one of them, along with the singleton pattern.
« Last Edit: March 11, 2009, 04:47:48 PM by Snakey » Logged

I like turtles.
Will Vale
Level 4
****



View Profile WWW
« Reply #13 on: March 11, 2009, 08:40:02 PM »

I shouldn't really have to ask an instance or a class what it is. C++ already know what it is.

Definitely agree with you there Smiley

The scripting example is a good one - typically you write glue code (or use tools to write it for you) when interfacing C++ and script languages. Reflection helps with this - although it might not be as fast as custom glue, you only have to write it once for all reflected types to work. And usually the C++/scripting boundary is not a major performance issue since the interpreted language will be the limiting factor.

Quote
Is RTTI really flexible in the system you've designed or is it a perceived flexibility

I think it's flexible. Built-in types are predeclared to RTTI and there's some template magic to handle pointers to anything else RTTI knows about. You can add more atomic types, and have your classes participate by adding macros (like MFC, not that anyone's going to like that comparison...)

Code:
// Header
class MyClass
{
    SIL_RTTI_COMPOUND(MyClass, void);
public:

private:
    int my_member;
    float my_other;
};

// Cpp
SIL_RTTI(MyClass, SIL_MEMBER(my_member) SIL_MEMBER(my_other))

This creates a singleton (and this is a legitmate use, honest) class called MyClass::Type derived from rtti::CompoundType<MyClass> - nearly all the injected functions and data are inside this nested class to reduce clutter. Note that your compounds don't have to be derived from a special base class. This is all implementation detail though - to get at the RTTI features, you call:

Code:
// Singleton instance of type. Because each C++ type is unique.
const IType& type = rtti::Type<MyClass>();

There aren't any enums or strings to maintain, the IType instances *are* the RTTI data, and you only get one per participating type so you can just compare addresses to see if you have equal types.

Quote
The ultimate test to that is, if you change your RTTI or change the way RTTI is represented, how much other code is affected (coupling).

Good test! If you remove a add/remove/change a feature of the RTTI system, the clients of that feature will need to change to use the new API - this is fair enough. But the classes participating (like MyClass above) won't need to change. I've tested this quite a bit since I've been slowly evolving the system over time.

Quote
I stress however, that this is my personal view and opinion.

Ditto - I was just interested to see if this stuff was popular in the indie C++ commmunity - it certainly comes up in commercial projects quite a bit. I also suspect that your approach will be *much* faster than since it's less indirect, but RTTI/reflection isn't usually a high-performance area.

I must also admit that it's an area I like for technical reasons - part of the reason I write stuff like this in my own time is because I enjoy it, so take it with the proverbial pinch of salt Smiley
Logged
nihilocrat
Level 10
*****


Full of stars.


View Profile WWW
« Reply #14 on: March 13, 2009, 10:03:49 AM »

I work primarily in Python so I'm probably not adding much to the discussion except a naiive perspective. Having duck typing (or if you want to put it in a negative light, "surprise typing") forces you to keep the scope of variables fairly tight or they become hard to manage. Having reflection and various other high-level doodads provides you with a whole lot of ability to "hand wave" your way past issues (because the program will figure out the truth at runtime) instead of getting stuck in various typing-related morasses. It's also really handy to take the __dict__ member of an object, which is basically a map containing all the members and methods of that class, and be able to modify your classes at run-time. I use this so that I can write my configuration files in Python itself, which simply update() the __dict__ with whatever instance variables / methods it should be using.

In various cases where you want to replicate templating, it's suggested you don't use isinstance(), but instead use some sort of try/except block, perhaps with assertions, which sees if a given variable behaves in a way that the function will be okay with (much like RTTI seems to be doing). It sounds like a lot of fanfare for something really simple, but on the converse side doing templating and having to worry about static types at all is a whole lot of fanfare.

I can see how people could take hardline stances on RTTI, because it looks to me like a sort of duck/dynamic typing in C++. Some people crash and burn without a compile-time type safety net, others burn out when they have to do a lot of type-related bookeeping on everything they write.
Logged

Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic