Should I use RTTI?

G

Grizlyk

I'm not sure what you are saying here

// *** library module

//object
struct Base{ vritual void method()=0; };

//Most parts of user code
struct User
{
virtual void user1(Base& obj){ obj.method(); }
virtual void user2(Base& obj){ obj.method(); }
};

struct Creator
{
virtual Base* create_base()=0;
virtual User* create_user()=0;
};

// *** user module

struct userBase: public Base { void adjust(adjust_list); void method();
};

struct userUser: public User
{
//to get extended interface of derived via pointer to its base class
void user2(Base& obj)
{
userBase *const tmp=dynamic_cast<userBase*>(&obj)
if(!tmp){ obj.method(); return; }

tmp->adjust(adjust_list1);
obj.method();

tmp->adjust(adjust_list2);
obj.method();
}
};

struct userCreator: public Creator
{
Base* create_base(){ return new userBase;}
User* create_user(){ return new userUser;}
};

// *** another user module

extern Creator& extCreator();

//Most parts of user code
int main()
{
Base &base=extCreator().create_base();
User &user= extCreator().create_user();
...
user.user1(base);
...
{
userBase tmp
tmp.adjust(adjust_list3);
user.user1(tmp);
}
...
user.user2(base);
}

It is due to creator and user are not the solid object and they are
communicating each other via pointer to base class.
 
J

Jorgen Grahn

The C++ standard does not guarantee this (which is my point). In
practical reality, since everyone intuitively expects it, compiler
writers generally try to support it.

C. Scott argues (I think) that since the standard doesn't mention dynamic
linking, it should work like normal (static) linking.

You seem to argue that since the standard doesn't mention dynamic linking,
all bets are off.

Seems to me that this is the compiler/OS vendor's problem. They have to
state "our dynamic linking scheme is broken WRT C++, if you look at it as a
special case of linking".

/Jorgen
 
E

Evan

Well I've got a problem, that is more theoretical than practital. I
need to know benefits of RTTI. I see another way of doing it...

class A {
public:
~virtual A() {}

enum Type { X, Y, Z };

Type GetType() { return type_; }

private:
Type type_;
}

class B : public A { ... }

and now if I want to know what type of class it is I just call a
GetType method.

I'd be really pleased with any advice. Thanks.

There are a couple answers, mostly what other people have said. I'll
just mention them to contrast what I'm going to say:
1. Your design is potentially faulty -- this is applicable in either
case
2. If you KNOW you have a fixed set of types, this can work okay --
but it's increased maintanence
3. So if you must use this design, RTTI is usually better

I'll just mention an interesting case where this doesn't necessarily
apply: generated code. If your class heirarchy is generated, then
maintenance isn't an issue, and an approach similar to this can have a
couple useful benefits.

I'm working with a C++ front end now where the classes that represent
nodes of the abstract syntax tree (AST) are generated from a compact
description like the following (abbreviated):

class Statement {
-> S_if;
-> S_for;
-> S_while;
}:

This would get expanded to:

class Statement { ... };
class S_if : public Statement { ... };
class S_for : public Statement { ... };
class S_while : public Statement { ... };

(In reality, the AST language allows you to specify data and function
members, but I'm leaving them out for simplicity.)

But it also generates essentially what you name. Statement contains:
enum Kind { S_IF, S_FOR, S_WHILE, NUM_KINDS }:
virtual Kind kind() const = 0;

Now this already has an unexpected benefit: while debugging in GDB, if
you have a Statement object s, you can see what kind it is by saying
"print s->kind()". I'm no GDB expert, but I don't know a way to do this
in general.

But there's more. It also generates more functions in Statement:
bool isS_if() const;
S_if* asS_if() const;
S_if* isS_if() const;

The former acts as you'd expect. The last acts exactly as dynamic_cast,
though I would guess faster. The second contains an assert(isS_if())
before returning. (There are also const versions of all of these.)

So depending on what you're doing, you have a syntactically cleaner way
of getting the downcast pointer than dynamic_cast.

This arose because the author was working in C++ before stuff was
standardized (let alone reliably implemented in compilers), though I
think he still prefers to use it, probably because of the cleaner
syntax.

Again, this is really only a good idea if you have auto-generated code.
In this example, the entire AST heirarchy comes out of the AST language
specs, so this isn't an issue.

-----


But this is a good place to ask this question, still working off of the
above. So I've got an AST, and I have classes that implement a visitor
interface that works like the GoF visitor. Except that my visitor only
has functions of the form like visitStatement, visitExpression, etc.
rather than visitS_if(), visitS_for(), visitE_funCall(), etc. This
means that my visit statement is more or less a "switch(s->kind()) case
S_IF: ..." type of thing.

I know the typical way that you're supposed to refactor this sort of
design smell (type tags) is by changing it to virtual methods. In this
case, there is a virtual function (e.g.) Statement::traverse(Visitor&)
each subclass implements to visit its children. (So
S_if::traverse(Visitor& vis) calls vis->visitStatement(this) then calls
traverse on the statements representing the branches.) I could see
changing this to call, say, visitS_if() instead of visitStatement().

However, suppose I can't do this. (As is pretty much the case;
implementing this would require changing the astgen program, and I
don't want to get into its internals.) Are there any better designs
that jump out?

In this case (again because the list of classes is very static) I don't
think that this is a big deal, and it's only mildly ugly, and I can't
come up with anything better, but it still smells a bit.

Evan
 
E

Evan

Evan said:
There are a couple answers, mostly what other people have said. I'll
just mention them to contrast what I'm going to say:
1. Your design is potentially faulty -- this is applicable in either
case
2. If you KNOW you have a fixed set of types, this can work okay --
but it's increased maintanence
3. So if you must use this design, RTTI is usually better

I'll just mention an interesting case where this doesn't necessarily
apply: generated code. If your class heirarchy is generated, then
maintenance isn't an issue, and an approach similar to this can have a
couple useful benefits.

Reading over this again, I think I was a little unclear. My example has
the same design smells as always; autogenerated code doesn't make that
go away. What I meant by "this" in "this doesn't necessarily apply" is
the problem with type tags where you have to modify the original class
when you add a new subclass. This is where the maintenance problem with
that approach comes up as compared to dynamic_cast.

BOTH approaches could probably be improved with virtual methods.

(BTW, I also realized after I posted that this sort of type tag
switching in my example is essentially a replacement for not being able
to do virtual dispatch on the type of an object passed as an argument
rather than this.)

Evan
 
F

fungus

I see another way of doing it...

class A {
public:
~virtual A() {}

enum Type { X, Y, Z };

Type GetType() { return type_; }

private:
Type type_;
}

That's basically what RTTI does, except the compiler
does it for you so it's neater, more reliable, smaller
(the variable "type_" isn't needed).

If you start taking out everything which you think is
only "syntactic sugar" then you'll end up with assembly
language.

Virtual functions can also be emulated via pointers,
why not get rid of them...?

If we follow this line of though to its conclusion we
end up with assembly language (or even hex keypunches
if you're an old dog of a programmer).


--
<\___/>
/ O O \
\_____/ FTB. For email, remove my socks.


We’re judging how a candidate will handle a nuclear
crisis by how well his staff creates campaign ads.
It’s a completely nonsensical process.
 
D

Daniel T.

Your code has a poor design. If the userUser class can only work with
userBase objects, then define it as such. If it can work with all Base
objects, then it shouldn't need to dynamic_cast the Base object it has.
 
D

Daniel T.

fungus said:
What if the function "callFunc()" is only defined in the derived
class? You need a pointer to the derived class in order to call it.

If 'callFunc()' is only defined in the base class and 'foo' needs to
call it to do its job, then 'foo' needs to be passed a 'SubType&'
instead of a 'Type&'. If 'foo' doesn't need to call 'callFunc()' to get
its job done, then it shouldn't call it.
 
G

Grizlyk

Daniel said:
If the userUser class can only work with userBase objects,
then define it as such.

For the first, userUser class can work not only with userBaseit can work with any Base.

For the second, I am not shure what have you offered as "define it as
such".
Can you make concrete improvements to my code example?

Bt the way, answer to the topic question is "yes, if you want to use
dynamic_cast, typeid and exception".
 
T

Tony

fungus said:
(e-mail address removed) wrote:
That's basically what RTTI does, except the compiler
does it for you so it's neater, more reliable, smaller
(the variable "type_" isn't needed).

If you start taking out everything which you think is
only "syntactic sugar" then you'll end up with assembly
language.

Virtual functions can also be emulated via pointers,
why not get rid of them...?

Because they are not hard-to-implement-in-the-compiler-things
that only those with billion dollar budgets can do? All compiler
machinery is not equal.

Tony
 
D

Daniel T.

Grizlyk said:
Can you make concrete improvements to my code example?

An attempt to compile your 63 line (including comments) sample produces
19 errors and 2 warnings. With that kind of error rate, I'm not even
going to pretend to know what you think the code was supposed to
accomplish. As such I can suggest no improvements.

As to the appropriateness of down-casting, I suggest such a discussion
is unsuitable for comp.lang.c++ and should be moved over to comp.object
instead. IMHO, if you simply cannot find any other solution to the
problem, then by all means down-cast, but there is likely a much more
elegant solution hiding there somewhere.
 
K

kwikius

Ron said:
Actually, it's probably a defect in your design if you EVER
have to ask what an object is. If there is behavior that
is dependent on the type of an object, it probably should be
implemented in a virtual method of the class.

That assumes that you had perfect foresight when you designed the
class.
In practise beware of theoretical dogma and use dynamic_cast if its
useful.

Use of dynamic cast is non intrusive.
This means that you can add functionality without disturbing previous
code.

You can look at dynamic cast as a query as to whether an object
provides a particular interface. For example:


#include <iostream>

struct object{
virtual ~object(){}
virtual const char* id() const = 0;
};
// old code
struct line : object{const char* id() const{return "line";}};
struct circle : object{ const char* id() const{return "circle";}};

// new code

struct colour{};
struct colourable{
virtual ~colourable(){}
virtual void set_colour( colour )=0;
};

colourable* as_colourable( object * p)
{
return dynamic_cast<colourable*>(p);
}
bool is_colourable(object* p)
{
return as_colourable(p) !=0;
}

struct filleable : colourable{
virtual ~filleable(){}
bool fill(){return m_fill;}
virtual void set_fill( colour )=0;
private:
bool m_fill;
};


filleable * as_filleable(object * p)
{
return dynamic_cast<filleable*>(p);
}
bool is_filleable(object * p)
{
return as_filleable(p) !=0;
}

struct line_v2 : line , colourable {
void set_colour( colour ){}
};
struct circle_v2 : circle , filleable{
void set_colour( colour ){}
void set_fill( colour ){}
};

void enable_menu_item(std::string const & str, bool value){}
object * get_selected(){return 0;}
void pre_menu_popup()
{
object * p = get_selected();
// new functionality
enable_menu_item("set colour",is_colourable(p) && !is_filleable(p));
enable_menu_item("set fill colour", is_filleable(p) &&
as_filleable(p)->fill());
enable_menu_item("set outline colour ", is_filleable(p) );
}

void get_item_interfaces(object * p)
{
std::cout << p->id() << ' '<< (is_colourable(p)? "is":"is not") << "
colourable\n";
std::cout << p->id() << ' '<< (is_filleable(p)? "is":"is not") << "
filleablle\n";
}
int main()
{
line l1;
circle c1;
line_v2 l2;
circle_v2 c2;
get_item_interfaces(&l1);
get_item_interfaces(&c1);
get_item_interfaces(&l2);
get_item_interfaces(&c2);

}
 
G

Grizlyk

Daniel said:
An attempt to compile your 63 line (including comments) sample produces
19 errors and 2 warnings. With that kind of error rate, I'm not even
going to pretend to know what you think the code was supposed to
accomplish. As such I can suggest no improvements.

I suggest such a discussion is unsuitable for comp.lang.c++
and should be moved over to comp.object instead.

Ok. To descrease error rate any can remove or comment "..." in
function's bodys, define parameter macros and so on. But I do it, and
post it to comp.object as "avoid cast to derived"
http://groups.google.com/group/comp...2f34216dbd6/5093804b2c7b74c3#5093804b2c7b74c3
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,021
Latest member
AkilahJaim

Latest Threads

Top