Solved

Run Time Type Info (RTTI)

Posted on 1998-10-23
25
334 Views
Last Modified: 2012-05-04
I want to write a class that has a function (say Add) which

1. accepts pointer to ANY object and a number. This no. is ID of object which will be unique for each object.
2. Find's it's type
3. class should maintain number of std::lists. each list is supposed to store objects of a single class. Function CREATES a new object of same type as that of the received object. copies all attributes from original object and stores newly created object in appropriate list. If List doesn't exist, It should be created.


Class should have other function (say retrieve) which receives a no (ID) and returns reference to previously added object with same ID.

0
Comment
Question by:gbipin
  • 8
  • 8
  • 4
  • +2
25 Comments
 
LVL 22

Expert Comment

by:nietod
Comment Utility
This cannot be done through RTTI alone.  In fact this can't be done as you want, but I suspect that what you need can be done.

details follow.
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
Just a few of the problems with what you propose:

RTTI only works (dynamically--which is what you need) on objects of classes that have virtual functions.  Thus it can't help with ALL objects.

For a procedure to take a pointer to any object, the procedure would have to take the pointer as an untyped (void *) pointer.  You can't use RTTI dynamically on a (void *) pointer.

RTTI allows you to identify the class to which an object belongs.  It provides no mechanism by  which you can create objects of a class (nor could it).

You want the new function to create
"a new object of same type as that of the received object. copies all
attributes from original object"
That would only work on classes that have a default constructor and an assignment operator (or similar function)  So again it can't work on all classes.

continues
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
But this type of thing tends to be done, but with some limitations (that are actually probably more benficial than they are limiting.)

There are two approaches I can think of:

The first is to use a class hieararchy.  Would it be acceptible to do this for any class that is within a particular class hierarchy?  (but not for classes that are not in the hierarchy).  Frameworks that use this approach simply make all (or nearly all) the classes in the hierarchy.  This is a good design for many reasons.

The second is to used templates.  This would allow you to store objects of any type (so long as the template doesn't use procedures the object doesn't support, like the default constructor)  Retrevial might be a problem.  I'm not sure how to handle that at the moment.  I'm just offering that as a possiblility in case the first one won't work.  But I would go for the first one if possible.
0
 
LVL 5

Expert Comment

by:yonat
Comment Utility
I wonder: Are trying to write a database or a database access layer? If so, I can provide some good pointers on this subject.
0
 
LVL 5

Expert Comment

by:yonat
Comment Utility
I wonder: Are you trying to write a database or a database access layer? If so, I can provide some good pointers on this subject.
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
What do you mean by "database access layer"?  For storing objects in a database?  Or for accessing a database?  Or...?
0
 
LVL 5

Expert Comment

by:yonat
Comment Utility
Yes - for storing, retreiving, updating and deleting objects persistently. This can be done either by writing your own persistence mechanism (database) or by interfacing with an existing persistence mechanism (database access layer).

It looks as if gbipin is trying to have copies of objects in a "different place", and I thought he may be trying to store them persistently. This can be a very difficult thing to do, especially if you have inheritence and/or complex relations between objects, so I thought I'd offer some help on doing this, regardless of RTTI.
0
 

Author Comment

by:gbipin
Comment Utility
Thanks a lot for your answers and comments but I find this answer doesn't solve my problem.

1. I have NO Class Hierarchy available. (That's why I mentioned Any Object!) So I can not use your first approach.
2. Please Elaborate on your second approach and I think I will get the solution.

So please explain in detail, the second approach.

Any way, thanks a lot.
0
 

Author Comment

by:gbipin
Comment Utility
To Yonat,

I appreciate that you have very correctly identified my purpose. I wish to store the objects in Object store database.

Please provide me more info / approaches in this matter.
0
 
LVL 5

Expert Comment

by:yonat
Comment Utility
OK. I collected some good (IMHO) info on this at http://www.kinetica.com/ootips/persistent-objects.html . Here is a breif overview - more details there:

1. Persistence and encapsulation are conflicting forces. To achieve persistence, you usually need to know the inner structure, and keep this structure stable. Encapsulation tries to hide the inner structure, and allows you to change it easily. Therefore, acheiving persistence in OO is difficult. Usually, the best way to go about it is to have a "persistence layer" where you keep all of the ugly stuff (it's ugly since there are conflicting forces involved). The application should not "know" about the details of this layer. It seems this is what you were trying to do.

2. Writing a persistence leyer is hell. It is probably better (cheaper, faster, safer, etc.) to buy one. The page above contains a list of products that provide OO persistence using a relational back-end. You may also consider using an OODB.

3. If you do have extra time or want to bang your head on this wall, there are many articles about how to do it. Again, the page above has many pointers. My favorites are: http://www.ksccary.com/ordbjrnl.htm , http://www-db.stanford.edu/pub/keller/#Object .

BTW, nietod answer to you question was very complete IMHO. The basic fact is that you don't have reflection in C++, and nietod suggestion is as close as you can get to reflection.
0
 

Author Comment

by:gbipin
Comment Utility
To Yonat,

Thank's for help. I am already using ObjectStore which is OODB. since, for storing objects dynamically, I need this layer. I have no other option but to bang my head on this wall.

Links are really helpful.

I think you are right, I really might not have reflection in C++. But You see, that's why I have posted the question.

Thanks again.
0
 
LVL 11

Expert Comment

by:mikeblas
Comment Utility
You can't do this without a class hierarchy.  You must know _something_ about the object.  Otherwise, you can't create it.  Even if you hooked up a way to create it, you'd next want a way to initialize it.

B ekiM

0
IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 
LVL 22

Expert Comment

by:nietod
Comment Utility
It could be done with out a class hierarchy, but I wouldn't recommend it.

I have object permenance in my library (within a class hierarchy), but what I do that is a little unusual is I have a utility that reads the C++ source for the class defintions and produces data structures that descibe the class format. Tthese data structures are then compiled into the project and are used to automate most of the tasks involved in the object permenance procedures.  This sort of approach could be used to implement object permenance on any class with a minimal amount of effort on the programmer's part.  (i.e. new classes could be added and could have permenance without additional programmingj.)  But I still would not recommend it too strongly.
0
 

Expert Comment

by:Eliezer
Comment Utility
When I wrote a system that solved this problem, I had to write an entire C++ reflexivity system before I could even start on the persistence.  This took a nontrivial amount of time.  I very strongly recommend you do this in Java 1.1 or 1.2 if you can, since Java already has these utilities.

Say, nietod, where does that utility come from?  It wouldn't work for my purposes without major modification, but it might still be interesting.
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
I wrote it.  It does considerably more than what I described as well.  
0
 

Expert Comment

by:Eliezer
Comment Utility
Sheesh, does every C++ guy need to write reflexivity?  Maybe it's time to consider a language extension.
0
 

Accepted Solution

by:
Eliezer earned 200 total points
Comment Utility
You can do this - get sizeof() and typeid() off a void* - but it takes black magic.  You can call a standard virtual function off a void* without a class hiearchy, but that takes more black magic.  And you can even do this with multiple inheritance, but then you actually have to sell your soul to the devil.  Ready?

The details given are for the Metrowerks PowerPC implementation of C++.  I don't know how portable they are.

--

If it's a pointer to a non-virtual structure, you can get the size like this:

void* theNonV = new CNonVirtual;
long theNonVSize = (*(((long*) theNonV) - 1)) - 4;

This relies on the code in New.cp, which stores a size stating how much memory was allocated in each piece of memory returned by the function new() calls to get memory.  This will return sizeof(CNonVirtual) rounded up to the nearest 4-byte boundary.

This will not work with items that are part of an array or that were allocated on the stack.

There is no way to get the typeid from a non-virtual void*.

--

If it's a pointer to a virtual class, you get the typeid like this:
void* theVirt = new CVirtual();
const long* virtualFunctionTable = *((const long**) theVirt);
const long* virtualFunctionHeader = virtualFunctionTable - 2;
const type_info* theTypeid = *((const type_info**) virtualFunctionHeader);

Alternatively, you can declare this:

extern "C" { void* __get_typeid   (void*,long); }
  and then
theTypeid = (const type_info*) __get_typeid(theVirt, 0);

This has the further advantage of doing some error-checking in case the class wasn't compiled with RTTI.
The function in question is probably in a file named "RTTI.cp" or something like that.

--

If you're not using multiple inheritance, getting the size is also simple:

long theVSize = (*(((long*) theVirt) - 1)) - 4;

Otherwise, you need to do this:

long theVOffset = *(virtualFunctionHeader + 1);
void* theBaseVirt = ((char*) theVirt) + theVOffset;
long theVSize = (*(((long*) theBaseVirt) - 1)) - 4;

--

Suppose you want to call a virtual function?  

class CTheWayWeDoIt
{
    virtual void StandardPersistIO (MyParameters&) { }
};

Now, as long as StandardPersistIO() follows exactly the same format and is always the first virtual function declared in any base class, you might be able to:

((CTheWayWeDoIt*) theVirt)->StandardPersistIO(theStuff);

But then you will go to hell when you die.

If you're going to do this with multiple inheritance, you need to override the virtual function in any multiple inheritor so it dispatches stuff properly, and then you need to do this:

long theVOffset = *(virtualFunctionHeader + 1);
void* theBaseVirt = ((char*) theVirt) + theVOffset;
((CTheWayWeDoIt*) theBaseVirt)->StandardPersistIO(theStuff);

There's a way to avoid this using something called PTMF, which I use for automatic dispatching of multiply inherited overrides when there are unified copies of the same base class.  It's a bit complicated, however.

--

A note about multiple inheritance:
Don't do this:

class CMyMultiClass : CMyNonVirtualClass, CMyVirtualClass { };

Then the pointer to all the virtual stuff isn't the first data member and everything will screw up.  Do this:

class CMyMultiClass : CMyVirtualClass, CMyNonVirtualClass { };

--

Final note:  If you do this for a corporation rather than personal use, then after you are fired, the person hired to maintain your code will resign.
0
 

Expert Comment

by:Eliezer
Comment Utility
Oh, yeah, one more thing.  To create a copy of the object, do this:

void* copy (const void* objptr)
{
  long newSize = BlackMagicSizeof(objptr);
  void* newMem = ::operator new(newSize);
  ::memcpy(newMem, objptr, newsize);
  return newMem;
  // You can delete this object the same way you would delete objptr.
}

If you have any pointers-to-self, this will smash them.  In this case, you may want to call:

  ((CTheWayWeDoIt*) objptr)->CopyInto(newmem)

#include <new.h>
void CVirtualClass::CopyInto (void* inData)
{
  new (inData) CVirtualClass (*this);
  // This is how you call a constructor on preallocated data.
}

--

With respect to the rest of your question, lists sorted by C string (from typeid().name()) and ID should be fairly standard code; look at the C++ standard library.
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
>> This relies on the code in New.cp, which stores a size stating how
>> much memory was allocated  in each piece of memory returned by the
>> function new() calls to get memory.  This will return
>> sizeof(CNonVirtual) rounded up to the nearest 4-byte boundary.
That is implimentation defined.  

>>This will not work with items that are part of an array or that
>> were allocated on the stack.
>> There is no way to get the typeid from a non-virtual void*.
These are serious problems.  

 . .  forget it.
0
 

Expert Comment

by:Eliezer
Comment Utility
I said it took black magic.  I said that it was for a particular implementation.  I said that the person hired to maintain his code would resign.

But on the other hand, it might work, and it would work a lot faster than writing a preprocessor (as you did) or a registration system (as I did).  Quick and dirty solutions are OK as long as they are clearly labeled as being quick and dirty.  Besides, let's all remember that his request is in fact impossible according to the C++ language and it is hardly likely that an implementation-independent way of cheating is possible.
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
But I don't think you can realistically get a solution like this to work.  How do you handle virutal constuction, for example?    Remember this has to work for ALL types.   How do you get RTTI on all objects, even ones that don't have virtual functions (even the POD types.)?  
0
 

Expert Comment

by:Eliezer
Comment Utility
I explicitly said that you couldn't get typeid() off of Plain Old Data.  That's impossible even if you cheat.  But what if gbipin doesn't really need it?  If the data has no virtual type, maybe all he needs to know is the size - in order to reconstruct it from a stream, and cast it to whatever type he likes.  POD has no provenance.  Alternatively, if he really wants RTTI, he could make his POD virtual.

Virtual construction needs either a "new (void*) ()", or copying the pointer to virtual-function-table along with everything else, both of which I cited in my supplementary comment.

Oh, and since this is a persistence system, gbipin might want to create a static "seed" copy of each object so he'd have something to copy the virtual function table from.
0
 

Expert Comment

by:Eliezer
Comment Utility
If gbipin is willing to put a SPersistData* member at the top of every structure and have every virtual class inherit from it, and then override every constructor to set it up, then any void* could always point to an SPersistData*.  Sort of like a custom vtable.  Of course, this will cost him in overhead, and it isn't really different in any significant sense from having everything inherit from QPersistent*, except that it would take more work.

What I proposed is piggybacking on the pre-existing RTTI overhead to make it act like a common base class - using universal "inheritance" from the allocation/RTTI runtime code instead of a formal class.  Of course this requires mucking with runtime code; how could it be otherwise?  As I see it, gbipin has six options:

1.  Preprocessor.  (Extracting information from source.)
2.  Registration.  (Custom source that extracts information in each case.)
3.  Common base class.  (The Sane Solution:  Use polymorphic language to support polymorphic behavior.  But sometimes you can't rewrite all the code.)
4.  Screwing with the runtime code.  (The Cheating Solution:  Extracting information from existing overhead.  No overhead, no preprocessor, no rewriting, but total implementation dependence and extreme fragility.)
5.  Self-analyzing assembler.  (The Deranged Solution:  Extracting information from data in its least comprehensible form.)
6.  Move to Java, which contains well-tested implementations of all the features needed.
0
 

Author Comment

by:gbipin
Comment Utility
Eliezer,

You have solved My problem.
As you said this implementation is really not for Corporates, but it's ok as there is no other way for me. I have understood your solution only. can you / Nietod please elaborate on other ways? Solution no. 3 and 6 are not possible for me. Still I would like to know how can I do if I had privilege to implement these solutions.

Thank you very much.

I have gone through your and Nietod's profile and I am Very impressed. I would like both of you to be my friends. Please write me at gbipin@yahoo.com.

P.S. : discussion between you and Nietod was very helpful. Thanks!
0
 

Expert Comment

by:Eliezer
Comment Utility
Okay.  First, a more careful definition of the problem:

In order to make a particular object persistent, it is necessary to make each member persistent.  This requires that, at some point or another, the program know how many members there are, where each member is located, the type of each member...  (However, if the objects are simple data structures not containing pointers, it may not be necessary to break them down into members; all that may need knowing is the size and type.)  This type of pattern, in which a program contains information about itself, is known as "Reflexivity".

Reflexivity is the key to persistence because it reduces the number of special cases.  Where before you might need to write a separate persistence function for each type, you can write persistence functions for the building blocks, for each type of member, and then dissect the object into building blocks.  Reflexivity solves persistence through reduction.  In your case, rather than needing to write a separate function saving each size of structure, it becomes possible to write a function handling any size of structure which can customize itself to any particular structure you hand it.  Reflexivity solves persistence through adaptivity.

The six solutions may be elaborated as follows:

1.  Preprocessor.
      (Nietod, who has implemented this type of system, probably knows a lot more than I do - I'm just guessing.)  This requires that you find (or if necessary, reinvent) some of the higher lexical stages of a C++ compiler, and that you have a development tool that lets you run preprocessors.  What the preprocessor does is look at your class definition and compile a set of usable facts about that class - size, members, type of members, and (probably) some special comments that carry additional information about each member.  The preprocessor then generates C++ code that registers this information.
      Nietod finds out the type of an object through RTTI and the typeid() function, or through a virtual function that returns his preprocessor-generated data.  He can then get a breakdown of the class by members, and then read and write each member separately, thus obviating the necessity for a PersistentSave() and PersistentRead() member to be overridden in every derived type.

2.  Registration.
      This is the solution I implemented, since when I started writing the system I wasn't quite confident to tackle preprocessor code.  (Eventually, I probably will.)  Each class has a static function, which is registered during static initialization, but actually called during main().  This function consists of a series of macros such as AddMember_(mFooMember, 'mFoo') or AddParent_(CParentClass).  Within these macros, a lot of offsetof() and sizeof() and static_cast(-1), and similar information-extracting code, is probing the class to determine its makeup.  I do have to write a series of macros for each class, but they are generally fairly short.
      After that, I can pass a pointer to a QPersistent* and register it with an ID. Then I get the real type of the object, and the offset, size, type, and ID of each class member.  I read or write each member in succession.  If it's another QPersistent*, I write the ID (if it has one) or recurse on writing it.  Note that IDs are assigned before I start writing the members, and that I write them to RAM - they only get written to disk when all the members are completed.

3.  Common base class.
      Most of the commercial solutions do this.  Anything persistent has to inherit from QPersistent*.  There's a pair of virtual functions called Read and Save.  Every class containing new data has to override these functions to I/O all the members, and it also has to register a static function that creates new objects.  Some of these systems don't take to multiple inheritance very well, and they require extra work for every single persistent class.
      Persistent reference networks are generally handled by universal "QPersistent* ReadPersistentObject(InStream&)" and "void WritePersistentObject(QPersistent*, OutStream&)".
      The reflexive data manifests as a program you rewrite for each object that calls the inherited functions and I/Os any new members added.

4.  Screwing with runtime code.
      If all you need to know is types and sizes - if you have no self-referential or other-referential members that can change between read and write - then you may be able to read the reflexive data from the compiler's RTTI and memory management.  Restoring virtual objects during "Read" may require that you have one example of that object on hand, so you know where the virtual function table is.  Working with reference networks may require "fake" virtual calls that wind up being so ugly that it eventually becomes easier to go with #3.
      Since RTTI and memory management vary from system to system, your code won't be very portable.  Multiple inheritance can be unpleasant.  Since you're using the seamy underside of C++, rather than the clearly defined language itself, your code will break if you touch it with a feather.  You aren't really doing C++ programming, you're dancing with the compiler.

5.  Self-analyzing assembler.
      In theory, your naked code contains everything your code needs to know about itself, in that a human who spent three or four years decoding it could probably figure out what the class structure was.
      I did in fact propose this solution, but not for persistence.  I was claiming that a self-enhancing AI had to understand its own assembler, because otherwise it would always be stuck one layer away from reality, unable to deeply modify itself due to a set of imposed assumptions.
      It *would* take a full AI to do this, I think, unless there was some kind of reliable pattern created by the compiler.  Despite being "The Deranged Solution", it's probably the most powerful solution of all, as measured by doing interesting things to Life As We Know It.  On the other hand, it would probably take several thousand man-years to implement.

6.  Move to Java.
      Java 1.1 has a complete set of reflexivity and persistence solutions built into the language.  (One of the advantages of interpreted code.)  If you're not doing something that needs speed, or that has to be small - in short, if you can afford the overhead - you can just write the program in Java, and everything will pretty much be taken care of for you.  Of course, you will have to learn Java first, but it was disguised to look like C++ on the surface.
0

Featured Post

What Security Threats Are You Missing?

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

Join & Write a Comment

What is C++ STL?: STL stands for Standard Template Library and is a part of standard C++ libraries. It contains many useful data structures (containers) and algorithms, which can spare you a lot of the time. Today we will look at the STL Vector. …
Container Orchestration platforms empower organizations to scale their apps at an exceptional rate. This is the reason numerous innovation-driven companies are moving apps to an appropriated datacenter wide platform that empowers them to scale at a …
The viewer will learn how to clear a vector as well as how to detect empty vectors in C++.
The viewer will be introduced to the technique of using vectors in C++. The video will cover how to define a vector, store values in the vector and retrieve data from the values stored in the vector.

771 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now