Archived

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

Shadowdancer

Enforcing behaviour in derived classes (C++)

Recommended Posts

I want to create an "auto-registering" type-info system to avoid the niceties I would run into if I used RTTI, so I thought of this:
class WObject {
 private:
  static int NextTypeID;
  int ObjectType;

 public:
  // Constructor, destructor, ...

  static int getNextTypeID( void ) {
    int temp = NextTypeID;
    NextTypeID++;
    return temp;
  }

  int getObjectType( void ) { return ObjectType }
};
 
Now, all performance issues aside, I have a problem. Each derived class would need to get its own type ID when the first object of that class is created. I could easily do this by writing it into each constructor, but I''m looking for a way to enforce this behaviour (basically, I want to forget about it in all derived classes). Any idea how this could be done?

Share this post


Link to post
Share on other sites
One reason could be that RTTI is not guarenteed across all compilers or even different runs to output the same thing.
And as for OT''s question, um....no Unless you are trying to use this system for serialization, use the RTTI system...else:

You shouldn''t need a int for each instance for each class. Instead, you need just one int for each type of class. So, you should implement a virtual function:
virtual const char* name() {...} which returns the name.

Override in each child class. There''s no other way around that, ...well, there is another way, but it''s far more complex and you *still* have to ''register'' the class in one way or another.

Share this post


Link to post
Share on other sites
quote:
Original post by antareus
So why are you avoiding RTTI?


Simply because its cost is unpredictable and I''d have to use it a lot. Also because it wouldn''t really fit in and its use for identifying objects would be a kludge.

Share this post


Link to post
Share on other sites
I also can’t see any way to do what you are trying to do. If the derived classes are to be oblivious to the type system, then the base class somehow needs to handle it. And I don’t know how a base class can ever know which derived classes it has without getting help from those derived classes.

Since RTTI is built into the compiler, chances are it’s at least as fast any manual way of handling it. If you plan on using more than one compiler, you might have a compatibility issue as risingdragon3 said. But I’m not sure I know what you mean about it being unpredictable.

If you need to use it a lot , then you might rethink the design. If your program often needs to ask objects what class they are, it may not be very object oriented. Typically, in object oriented designs, code is together with the data it operates on so that it doesn’t need to ask what class it is except in a few cases. But then again, I don’t know exactly what you are doing. It’s just a suggestion.

Share this post


Link to post
Share on other sites
quote:
Original post by JimH
Since RTTI is built into the compiler, chances are it’s at least as fast any manual way of handling it. If you plan on using more than one compiler, you might have a compatibility issue as risingdragon3 said. But I’m not sure I know what you mean about it being unpredictable.


no, because every tiny class will get RTTI, which is a waste. manual RTTI on just the ''major'' classes imo is better.

Share this post


Link to post
Share on other sites
quote:

Seems like the typeid() feature of RTTI is exactly what he wants, but how is it not guaranteed to return the same thing after runs?


Well, that''s the worse case - it''s not guarented in the standard. With different compilers, you will get different output but ...if you just use one compiler, you''re good.

quote:

no, because every tiny class will get RTTI, which is a waste. manual RTTI on just the ''major'' classes imo is better.


Well, as far as I know, compilers only enable RTTI when the class is polymorphic... I''m not positive.

Share this post


Link to post
Share on other sites
quote:
Original post by billybob
no, because every tiny class will get RTTI, which is a waste. manual RTTI on just the 'major' classes imo is better.


Well, when I read the thread, for some reason I was thinking that Shadowdancer was most concerned with performance when he said “because its cost is unpredictable”. But reading it again, now I can’t say I’m sure specifically what his concerns are. Is it memory cost or performance cost?

From Effective C++ (which I refer to a lot) Scott Meyers says, “The language specification states that we’re guaranteed accurate information on an object’s dynamic type only if that type has at least one virtual function. ... This parallel between RTTI and virtual function tables is no accident: RTTI was designed to be implementable in terms of a class’s vtbl.”

So RTTI is very likely to be implemented in the vtbl, probably with an extra pointer in the vtbl which points to the class’s type_info. Classes (and structures) that do not have a vtbl are not likely to use up space for RTTI, although I imagine a compiler would be allowed to do so if it wanted to.

Anyway, you might want to test this more depending on the compiler you’re using. I just used Visual C++ to compile a small program both with and without RTTI enabled and it was exactly the same size. The program code didn’t actually use RTTI so perhaps the compiler was smart enough to realize that and not generate RTTI info where it wasn’t needed. (But I’d need to test it more to be sure. I don’t usually need RTTI so I’ve never looked at it closely.) Also, I believe you may be able to enable RTTI on a per-file basis if you want. In any case, I don’t believe it takes up significant space; it is per-class data only.

If I only needed type information for two or three classes, I might do as you say and roll my own. But Shadowdancer said he needed to use it a lot. The compiler’s type checking is more robust and almost certainly more efficient. If you want the functionality that run-time type checking provides, you’re going to have to pay for it one way or the other.

[edited by - JimH on May 5, 2003 1:36:24 PM]

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
isn''t it just easier to go

class BaseClass
{
protected:
int ID;
}

class DerivedClass : public BaseClass
{
DerivedClass(){ID = 1; };
}

etc...

Share this post


Link to post
Share on other sites
You can. Though you’ll also need a function to retrieve the value, such as int GetType(). So is the code and static member that you added going to be smaller and faster than what RTTI adds? Plus you have to manage all the type ID values yourself and you may make a mistake.

Anyway, Shadowdancer was looking for a way to do it without having to put any code—such as ID = 1;—in the derived class.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
The way its described up at the top will create an Id per object INSTANCE, not per class.

Anyone telling you that RTTI is not guaranteed to give the same result across multiple runs of the same binary is smoking crack.

Anyone telling you that there are modern C++ compilers that don''t implement RTTI properly is living in the late ''80s. (Hey, how''s that Miami Vice rerun coming along? Depeche Mode release anything new, lately?)

You want the C++ typeinfo and typeid system. These are implemented by the compiler strictly according to well-defined behavior specified in the language standard. The compiler is likely to be able to do as well as you do, or better, for any practical purposes.

Share this post


Link to post
Share on other sites
quote:
Original post by Anonymous Poster

Anyone telling you that there are modern C++ compilers that don''t implement RTTI properly is living in the late ''80s. (Hey, how''s that Miami Vice rerun coming along? Depeche Mode release anything new, lately?)

Actually, Depeche Mode has released a lot of new stuff, lately... :->



peace and (trance) out

Mage

Share this post


Link to post
Share on other sites
quote:
Original post by Anonymous Poster
Anyone telling you that RTTI is not guaranteed to give the same result across multiple runs of the same binary is smoking crack.



I don''t think anyone is saying that. What is a problem, is when your server runs on linux, and your client runs on Windows. Doh!

RTTI is not interoperable between systems at run-time.

Share this post


Link to post
Share on other sites
quote:
Original post by Anonymous Poster
Anyone telling you that RTTI is not guaranteed to give the same result across multiple runs of the same binary is smoking crack.



I don''t think anyone is saying that. What is a problem, is when your server runs on linux, and your client runs on Windows. Doh!

RTTI is not interoperable between systems at run-time.

Share this post


Link to post
Share on other sites
quote:
Original post by Anonymous Poster
isn''t it just easier to go

class BaseClass
{
protected:
int ID;
}

class DerivedClass : public BaseClass
{
DerivedClass(){ID = 1; };
}

etc...


This by itself is fragile and horrendous to maintain as the number of classes grows. You''d want to use a manually maintained enum, and then make the BaseClass read:


class BaseClass
{
BaseClass(int rtti_id) : rtti_id(id)
protected:
const int rtti_id;
};


So they are forced to set the ID, which will make it obvious when the enum needs to be updated.

Some creative meta-macro programming may solve this problem statically.

Share this post


Link to post
Share on other sites
Thanks for all the ideas, I''ll have to think about this some more I guess, but at least now I have some educated opinions

Just to clear one issue: I''m concerned about performance when I''m talking about cost here

Share this post


Link to post
Share on other sites
When I implemented my version of this, I took a lot of inspiration from the MFC sources...

BTW: If you want my implementation, email me and I''ll send it to you...

Share this post


Link to post
Share on other sites
Well, I have a question. If all you want is a static id per class instance, and you aren't adverse to something like this:

class derived : public base < derived > {};

You dont' need to implement _anything_ involved w/ RTTI, but you don't get a string constant. If you aren't adverse to a macro, then you can get a string.

It's creative meta-macro-programming, as MKH said. It's pretty nifty, I like it... and the ID is even at compile time. Useful. But ...probably not for you .
Anyway, if you want it, just post. I think it even works on MSVC6 (gasp).

[edited by - risingdragon3 on May 5, 2003 4:29:14 PM]

[edited by - risingdragon3 on May 5, 2003 4:29:42 PM]

Share this post


Link to post
Share on other sites
Hmm... but doesn''t that mean you can''t do virtual functions/polymorphism, since each derived class would inherit from a different specialization of the base class?


How appropriate. You fight like a cow.

Share this post


Link to post
Share on other sites
Well, I was imagining that the template would be defined like this:

      
#include <iostream>
#include <conio.h>

namespace aux {
template <int n> struct size_ret {
char size[n+2];
};

size_ret<-1> series(...);

template <class T,int n> struct get_and_inc;

template <bool can> struct helper;

template <> struct helper<true> {
template <class T,int n> struct impl {
enum {value = n};
friend size_ret<n+1> series(size_ret<n>*);
};
};
template <> struct helper<false> {
template <class T,int n> struct impl {
enum {value = get_and_inc<T,n+1>::value};
};
};

template <class T,int n> struct get_and_inc {
enum {current_value = sizeof(series((size_ret<n>*)0))-2};
enum {is_null = current_value == -1};
enum {value = helper<is_null>::template impl<T,n>::value};
};
}
//assumes base_type is your "true" base

struct base_type {
virtual const int get_id() = 0;
};

template <class child_type> class base : public base_type {
enum {ID = aux::get_and_inc<child_type,0>::value};
virtual const int get_id() {return ID;}
};

struct child_a : public base<child_a> {};
struct child_b : public base<child_b> {};

using namespace std;

int main() {
base_type* ptr = new child_a;
cout << ptr->get_id();
delete ptr;
ptr = new child_b;
cout << ptr->get_id();
delete ptr;
getch();
}



...useful? I dunno. but definitely cool

EDIT: silly ;'s
EDIT2: Of course you would have to edit the recursive template to nibble at the recursion, this is not "industrial strength": depending on your compiler, you may hit the template recursive limit if you define more then say 60 types. If you nibble you can get as many as you want.
[edited by - risingdragon3 on May 5, 2003 4:41:43 PM]

[edited by - risingdragon3 on May 5, 2003 4:43:04 PM]

Share this post


Link to post
Share on other sites
I just realized that you can also check at compile time to see how many derived classes you have declared:

  
template <bool can> struct get_helper;

template <> struct get_helper<true> {
template <class T,int n> struct impl {
enum {value = n};
};
};
template <> struct get_helper<false> {
template <class T,int n> struct impl {
enum {value = get_and_inc<T,n+1>::value};
};
};

template <class T,int n> struct get {
enum {current_value = sizeof(series((size_ret<n>*)0))-2};
enum {is_null = current_value == -1};
enum {value = get_helper<is_null>::template impl<T,n>::value};
};


Interesting. This is like compile time variables, in a way. Hmmm...could I do subtraction?..I don''t think that''s possible...wait...yes..i could...
*tests*...I''ll report back.

Share this post


Link to post
Share on other sites