Link to home
Start Free TrialLog in
Avatar of lar_jens
lar_jens

asked on

C++ OO trick

I'm using Visual C++ 6.0 with RTTI on an 80x86 processor.

I want to create an Object class that is an ancestor of all other classes in my framework.
In my object class i want to create a method Clone() that returns a new instance with the
same data as the one clone is called from. I also want all other derived classes to inherit
the clone function, and thus be able to clone themselves without having to modify the
original clone function.

I have already done something simillar with a method named IsKindOf()

Something like this:

class CObject {

public:
  ...
  CObject* Clone();
  ...
}

CObject::Clone() {
  // Some inline assembly, perhaps making use of RTTI,
  // or more likely the way objects are laid out in memory
 
// Should be able to create a clone of any instance decending from this class
};

class CSomeClass : public CObject {
};


CObject* pObjectA = new CSomeClass();
CObject* pObjectB = pObjectA->Clone();


pObjectB should now be a pointer to an instance of CSomeClass.

Any ideas on how to solve this?


TIP:
Maybe it is possible to copy the vtable pointers and the data, and
thus create a simillar structure somewhere else in memory, and then
return a pointer to this new structure?
Avatar of lar_jens
lar_jens

ASKER

I think I remember something about an article in MSDN called C++ under the hood, regarding the way objects are laid out in memory. Anybody know where to find it?
This sounds so easy, but it's really stretching the boundaries of my knowledge. Copying the datastructure and vtable sounds like a hack to me and is rather vulnerable if compiled with different compilers. The best bet I could come up with after about half an hour of research was the placement new operator. Although I'm rather sure that it won't work, you may want to give it a try. A sketchy snippet of the CSomeClass::Clone() method will illustrate what I'm thinking of here:

CSomeClass* CSomeClass::Clone() {
    CSomeClass* pRet = new( CObject::Clone() ) CSomeClass;
    // additional cloning of objects specific to CSomeClass goes here
    return( pRet );
}

I have to admit that I'm not 100% sure about the workings of the placement new operator. As far as I know it doesn't allocate any memory but rather constructs an object at a given address, which would in turn cause memory to get corrupted once you try to access data that is not present in the CObject base class. Anyway, it may be worth a try.

.f
Well, U see, the point here is that you are not supposed to do anything in the derived class. Just inherit the functionality.

Anyway, the largest parts of the PC market is using Wintel today.

It is no problem to override the functinality in the CObject class like this:

CObject* CSomeClass::Clone() {
  return new CSomeClass(*this);
}

CSomeClass& CSomeClass::operator = ( const CSomeClass& other ) {
  ( (CObject&)(*this) ) =  (CObject&) other;
  // Specific assignment for CSomeClass
}

I doubt that your design decisions are ok, if you want to implement a function in a base class that will have to act differently in derived classes, but shouldn't be reimplemented there. I would suggest that you get the book "Design Patterns" and see if you are still on track with your design ideas.

.f
If you can add some paramters for the clone function, you can try this one.
CObject* CObject::Clone(int nSize)
{
     CObject* pNew = (CObject*)new char[nSize];
     memcpy(pNew, this, nSize);
     return pNew;
}
//...
CObject* pObjectA = new CSomeClass();
CObject* pObjectB = pObjectA->Clone(sizeof(CSomeClass));
//...
But I'd like to override all virtual "Clone" function in each sub-class of CObject.

Why are you all worried about my design decisions? I'm doing this for fun, not for profit!
To BeyondWu:

>> CObject* pObjectB = pObjectA->Clone(sizeof(CSomeClass));

Well, that won't work for me. I do not know in advance what kind of class pObjectA refers to.

Consider this function:

CObject* SomeFunction( const CObject* pObject ) {
 CObject* pLocalObject = pObject->Clone();
 printf ( "%s\n", pLocalObject->GetName() );
 return pLocalObject;
}

First of all, it isn't 'all of us', but only me. So I would assume that you have had a similar responses somewhere else. Second, fun turns into agony if you make a wrong decision in an early design stage. I mean, what is the fun if all you do is learn bad habits?

Last but not least, I for one don't make a difference between commercial and fun projects -- they are all the same quality, no matter whether I get paid for it or do it as an educational exercise. One never knows if a fun project isn't going to become a commercial application in the future.

Anyway, it's your decision.

.f
I'd like to use virtual function for it.
If you really want to do this, I think the problem is we can't know the size of the object.
would you like to add a datamember m_nSize in the base class, and then rewrite all the construtor of all the classes.
class CObject
{
        int m_nSize;
public:
     CObject()
     {
          m_nSize = sizeof(CObject);
     };

     CObject* Clone()
        {
          CObject* pNew = (CObject*)new char[m_nSize];
          memcpy(pNew, this, m_nSize);
          return pNew;
        };
};
What you want is impossible. You cannot have a baseclass take care of all possibilities that may arise without changing derived classes. Even if you get it to work for a subset of possibilities using RTTI, you will shoot yourself in the foot as soon as you use multiple inheritance. Plain and simple: it just doesn't work and appears to be a design issues rather than lack of implementational skills.

But since my feedback seems to be unwanted, I'm out now.

.f

How can the Visual C++ debuger know what to display in the watch window, if it doesn't know anything about the class it wants to display? The information the debuger displays must come from somewhere.

What I mean is this :

You know the size of the object if you use the heap functions, because the delete operator knows how much memory to release using these functions.

It is also possible to find out how classes are laid out in memory. Ask yourself how you would implement a C++ compiler for a yet unknown chip. If you know where the _vfptr is for that kind of class, and are able to copy the data, then you can create a dynamic clone() function in the object class.

You can create an IsKindOf() function in the object class that no derived class needs to implement using RTTI and a bit of assembly to override the parameters passed to the dynamic_cast<> operator, I've done this already ( actually it was just 8-10 lines of assembly code - easy to port ).

This, ofcourse, only works with the Visual C++ compiler on an Intel 80x86 processor (or AMD). But if you look at the market today, most people using a computer is using Microsoft Windows with an Intel/AMD processor. So if my fun project is going somewhere commercial then a lot of the PC market is ready for the taking :).

Nevertheless, I'm using the insight I hope to gain from this, to learn more about the underlying concept of programming OO from assembly (this is as you may have noticed an assembly group)

So what I am looking for is someone who knows something about the results generated from the Visual C++ compiler when programming with classes and objects.

PS : Multiple inheritance is not an issue. I won't be using it.!

To flOyd:
You'r feedback is wanted. It truly is. I'm just trying to elaborate on my question, so that all you guys in here knows what I'm asking for.

:):):)

The basic concept of a class and it's instance is like this:

You have a set of functions somewhere in memory. All of these functions take a 'this' pointer as a parameters (passed on the stack). The 'this' pointer, points to a memory structure that holds data for a given instance of a class. This memory structure also holds pointers to the functions that will alter the data.


If you define a member function like this :
void CObject::MemberFoo(int i, double d);

In reality it becomes something like this:
void CObject::MemberFoo(CObject* this, int i, double d);

-LJJ
lar_jens: "How can the Visual C++ debuger know what to display in the watch window, if it doesn't know anything about the class it wants to display?"

What sort of speculation is that? Don't jump to assumptions. For one thing, you have very likely compiled a lot of debug information into the executable being debugged, haven't you? Plus you have a program database sitting around the harddrive somewhere aiding the debugging process as well. With all this being helpful to the debugger, there is one thing you are missing: The debugger comes into play _AFTER_ _EVERYTHING_ has been compiled and linked, thus all the information you may ever need in the watch window is there. When writing your code in the base class, you may not even have though about a derived class yet. That is a big difference as far as information is concerned.

Another notice: The produced code isn't dependant on the cpu you are running but rather by the compiler/linker combination you are using. Compile the same code using gcc for example and you won't be able to link to and .obj file created with that. C++ name mangling isn't standardized at all, hence the need for a binary compatibility layer, COM being the most wide spread one.

So whatever you are trying to do, it will always be compiler/linker dependant. You don't even have a guarantee that a compiler's output will be consistent across versions, e.g. if you compiled source with msvc 6.0 you may not be able to link it with the linker provided with msvc.net.

So my suggestion remains unaltered: Check if your design decisions aren't getting in your way. It sure sounds like it. Whenever you have to jump to features that aren't part of the language's standard things are likely to give you a headache further down the road. Even if you are doing it 'just for fun'.

As for your last comment: That was the intermediate output of early c++ [pre]compilers that generated c-code to be fed into a c-compiler. On msvc 6.0 the this-pointer isn't part of the parameter list, i.e. it is not passed across the stack but rather in a register, esi, if I'm not totally mistaken.

.f
To floyd:
Thanks for the info on esi, or that the this pointer is passed in a register :) I will take a look at that. :)

To group:
But, even if I am stuck with VC++ 6.0. I've used that compiler for the last 3-4 years.. OK, so I have to convert some day, but for now.. I'm just learning :)

As an example the IsKindOf() function has saved me alot of work. :) And I thought that I could write a couple of functions like, Clone(), IsKindOf() and IsOfType() in the CObject class, and if I wanted to port my framework to another compiler/architecture, the only place to port would be in the CObject class.. :):) And new classes would not need to do anything, just surf on existing functionality :)


Right, put IsKindOf() in your CObject class and modify it each and every time you create a derived class? In what way are you hoping to get a superior system this way instead of overriding a virtual Clone()-function? This approach just doesn't make any sense, since it isn't your base class that would know how to clone a derived class but the derived class itself is the only one that would.

On the IsKindOf(): If you have used it extensively I would take a far guess and say that your class hierarchy and design is bad. I believe I have already stated above that it's use is limited and most of the times I've seen it in action was when the design of the class wasn't well thought out in advance and this hackish approach stepped in. There are several issues with RTTI, too, making it a major pitfall. The wrong order in your switch statements is all that is needed to screw up your entire code. You may want to get your hands on A. Alexandrescu's 'Modern C++ Design' and expand your knowledge on what's possible with 'legal' c++ features that few have ever thought about. It did surprise me to say the least.

.f
SOLUTION
Avatar of BeyondWu
BeyondWu
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
To floyd:

I think that you are missing the point here.. I put IsKindOf() in the CObject class, and then I do not have to do ANYTHING AT ALL when I create a derived class. It just works!! :):):)
To BeyondWu:
Thanks.. I'll get back to you when I have tested the code :)

To Floyd:
I have not created a single class in my framework yet, so it could not possibly be called a hack. A couple of questions for you:

Why does some people spend a great deal of time making scrolltexts and weird effects with bitmaps in assembly?

How come some people put a lot of effort into creating a program that can change the image in the boot-up screen in their bios?

Why was the first posting in here a question about who could create an assembly program with just ASCII characters in notepad, store it as a .COM file and be able to run it?
Why don't you make it a legal implementation using templates?

template<class T>
class CObject {
    T* Clone() { /* ... */ }
    // ...
};

class CDerivedClass : CObject<CDerivedClass> {
    // don't have to do anything in here to get Clone to work
};

Although I doubt it will compile with msvc 6.0 it is legal c++ and a clean way to achieve what asm hacks can only hope for solving in a limited field of scenarios. Sure, this templated version does have deficiencies as well, for one thing you cannot have static data in your base class. Well you can but it will not be shared across instantions for different template arguments.

To answer your questions:
* Why do people make funky effects using asm? I don't know, performance could be one reason although most of the times proper use of a high level language is sufficient. I have come across very few cases when asm did make sense. In particular I only really felt the need when I had to access cpu specific functionality which wasn't accessible through language features: clock-cycle-accurate timers/MMX/SSE/SSE2/3DNow!
* How come ... boot-up screen in their bios? Beats me, don't have a clue, really, I couldn't care less about images that I would come across about once or twice a month
* The notepad thingie: Challenge. Guess that's a good reason for just about anything. Just for the record, the smalles such app I have seen must be farbrausch's Five-In-Your-Face.

Anyway... I hope the templated version will suffice for what you are trying to attempt, although I'm somewhat saddened that you are more in search for something that absolutely has to be done in assembly, rather than getting a valid solution to your problem.

.f
Small correction: The derived class should be defined like this:

class CDerivedClass : public class CObject<CDerivedClass> {

Sorry for the confusion.

.f
Avatar of DanRollins
An interesting discussion!  Stand back from the ultimate goal and take a look:

An IsKindOf() function and a Clone() function would be SPECTACULARLY USEFUL.   Every foundation class that you could buy would offer it.  You could do a deep copy.  You could DO a DEEP COPY without manual labor!

So..... why does MFC not have it built into CObject?  Or OWL? or why isn't there an STL base object that does it?

I have to say... because it is not possible in the C++ programming language.

As I understand it, Java has such a feature.  So how does Java do it?  I'd guess that it is part of the language.  The Java compiler must carry around everything that needs to be known about the object and all of its ancestors.  It would need to know the class name and its size.  It would have to handcuff programmers by requiring only references and never pointers.  You understand that void* would be a dissallowed data type... right?  Pointer coersion would be expressly forbidden... and so forth.

Just some food fer thot.

<Dan/>
To DanRollins,

The C++ has, however, the dynamic_cast<> operator, which works much like the IsKindOf functions. But you must link with RTTI to make use of it.

:)

-LJJ
So there you have it.   The very fact that a separate CPU-soaking process must be used illustrates that it is not possible using normal C++ techniques.   As I understand it, RTTI stores data in the vtable which must be carried around and processed constantly.  Even simple copy constructors become bulky, slow affairs.  A reasonable guess why MFC does not use RTTI (and instead has the DECLARE_DYNAMIC and IMPLEMENT_DYNAMIC thing):  RTTI causes massivie ineffiency all over the place and made MFC seem slow.

You must use C++ with RTTI, or switch to Visual Basic, or Java, or some other less-than-totally efficient language if you want this capability.  I'm sure that it is built into C# and .NET along with the garbage collection etc.

<Dan/>
Hmmm, sorry, but this doesn't seem to be correct, Dan. RTTI is part of C++ and as such it is a 'normal C++ technique', unless you were talking about something else. RTTI is CPU-soaking? Not that I've ever heard of. It doesn't play a role in object construction nor is it data that has to be stored on a per-object basis. The objects merely have _one_ additional pointer in their v-table or somewhere else, an index into the per-type typeinformation database.

Btw, you can implement an IsKindOf()-function in c++ that retrieves information at compile-time. As stated in one of my previous posts, A. Alexandrescu has some information on this in his "Modern C++ Design".

.f
I was going on rumor -- I've often heard that turning on RTTI causes a performance hit, but I've never actually tried it myself.
-- Dan
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
To ssnet :

I have generated the isKindOf function like this (VC++ 6.0) :

// Function that compares the type of this instance with
// type information for a class
BOOL SDCObject::isKindOf (const type_info& other_type) const {

      // Declare a variable to hold the wanted result
      DWORD result = 0;

      // Declare a variable that will hold the
      // type information for this instance
      const type_info& this_type = typeid(*this);

      // Use inline assembly to call the dynamic_cast<> operator
      // given the type information we have collected.

      // We want to dynamically cast from the this_type
      // to the other_type (the variable to this function)

      // The rest of the code will be something like :
      // return ( dynamic_cast<other_type> (this) ) ? TRUE : FALSE;
      // in plain C++, but that is impossible without assembly

      __asm {
            ; Push variables on the stack
            push 0                                    ; (Not known)
            push other_type                        ; Cast to the other_type type
            push this_type                        ; Cast from the this_type type
            push 0                                    ; (Not known)
            push this                              ; Pointer to the instance we want to cast
            call __RTDynamicCast            ; Call the dynamic_cast<> operator
            add esp,014h                        ; Adjust the stack to the position before we pushed
            mov result, eax                        ; move the result into a variable
      }

      // Now we can find out if the casting was successful.
      // Return the result to the caller.
      return (result) ? TRUE : FALSE;
}
That's really cool code.... except for two things:  

1) It causes and an unhandled exception upon call __RTDynamicCast

2) Why drop down to ASM?  If you can find documentation on the RTDynamicCast library function, then why not not just use standard C syntax to call it?

-- Dan
To DanRollins..

This code works on my PC, and a friend of mine have tested it successfully on several PC's..

The reason I am using ASM, is because the following can not be done in C++ :
  return ( dynamic_cast<other_type> (this) ) ? TRUE : FALSE;

The dynamic cast operator can only be used with types available at compile time,
the assembly code solved this problem, so that I was able to use it at run time..

Also, the RTDynamicCast function is not a library function, it is the dynamic_cast operator
used from C++. The assembly code I have generated is very simmiliar to the assembly
code generated by the compiler, except that I use some extra code to get the type_info
of the this instance.

I use it something like this :

  if ( pObject->isKindOf( typeid (CButton) )
  {
    .. Handle button event
  }

PS : You have to turn on RTTI ( but I guess you already knew that ).
lar_jens:

I'm not sure who told you that dynamic_cast<> can only be used on types available at compile time. That is, quite frankly, rubbish. The dynamic_cast<> operator is explicitly there to check for type relationships at _run time_.

.f
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
fl0yd:

Yes, I know this, but...

Have you tried to do the following:


BOOL CObject::isKindOf ( parameter )
{
  return ( dynamic_cast<parameter> (this) ) ? TRUE : FALSE;
}

and if you have, please tell me how you solved the problem, cause I
would much rather use C++ than ASM on this one, if I can.

Remember -> The goal is the isKindOf function, not the dynamic_cast operator...
To fl0yd:

Thanx for the tip.. :)
I will try to rewrite the function so that it uses the msvcrt.dll instead..
And one more question to fl0yd:
Why all the sarcasm?
lar_jens,
Perhaps some sarcasm is due:  This thread was started over 4 months ago, then you dropped off the face of the earth.  Now you're coming back with this RTTI kludge as if it were some sort of solution to the problem that was originally stated.
ohhhhh.... buuuurrrnnn. :p

anyhoo: thanks for the isKindOf info... bold assembly man. thinkin outside the box.

i know what you were trying to do with the dynamic_cast, and i'll try to explain why it didn't work... basically, the casting operators take TYPES not instances, so

BOOL CObject::isKindOf ( parameter ){
 return ( dynamic_cast<parameter> (this) ) ? TRUE : FALSE;
}

won't work... (it SHOULD be doable with a properly designed typeid operator... but that's a different can o' worms)

what you need is something that takes a TYPE as a param... that sounds like a template to me... infact, i've had luck with something similar to what you have here... you're already using the typeid op, so no extra overhead is incurred with dynamic_cast... try something like this

class BaseClass {
      public:
            template<class T> bool isA () {
                  return (dynamic_cast<T *>(this) != NULL);
            }

            virtual void bargle(){}
};

the virtual function bargle() is just there to give us a vtable (dynamic cast only works on stuff with vtables, since it's for runtime casting and all)... if we don't want to compare types from different trees, we actually don't need this, because the compiler can do it at quote{compile-time}... assuming you have virtual functions in the root of your tree (almost certainly the case), you're fine and can use any type...

so you would use something like this as so:

BaseClass myBaseClass;

myBaseClass.isA<std::string>(); // false, unless you inherit from std::string
myBaseClass.isA<BaseClass>(); // true

i'm really not a fan of templates (i'd rather just go to an interpreted lang, or even use extrenuous casting, given the kludge of naming a full-out class template), but here it saves some typing... much like inline functions save typing (ie. the typist's overhead is small).

it's interesting to note from this that there are many cases that could and should be resolvable at compile time BY the compiler, and while this method works in the generic case -- for dynamically linked code, and statically linked code -- the statically linked relationships would be more efficiently resolved at compile-time... but then, this would require some sort of crazy compile_cast<>() that evaluates at compile-time rather than runtime, like a macro or something, or one that resolves at compile time only if the source scope resolves then... the effect simply being a compile-time way to test inheritence relationships, without having to dig through all the documentation... (wich may be too rediculous a desire to be important)

it's something i find interesting is all...

hope it helps...
The Problem with your isKindOf( parameter ) is a limitation of the language C++ -- it doesn't provide a data type that refers to a type, i.e. there is no 'type t1 = CButton;' in contrary to other languages like Lisp.

RTTI is a partial solution to the problem, but especially in this context there are cleaner ways since all types are known at compile time (if you want to check whether some object was derived from your generic base, CObject):

template<class T, class U>
class Conversion {
    typedef char Small;
    class Big { char dummy[2]; };

    static Small Test( U );
    static Big Test( ... );
    static T MakeT();
public:
    enum { exists = sizeof( Test( MakeT() ) ) == sizeof( Small ) };
};

Now you can do the following:

BOOL b = Conversion<CButton*, CObject*>::exists;

A bit of magic is going on here, but all within the rules of the language's standard. What the template does is basically exploit the compiler's ability to check for convertibility. If type T can be converted to type U, the function returning Small will be used. For all other cases the one returning Big will be invoked. MakeT() is needed to make sure the template also works for types that have a private/protected default c'tor -- otherwise a simple T() would have been enough. The last step is to compare the sizes of the returned values to see whether the compiler was able to convert T to U. [sizeof( Small ) is guaranteed to be 1, whereas sizeof( Big ) is implementation defined and can be anything. It will always be larger than 1 though.]

.f
DanRollins,

I simply answered the following :

>> i think of greater interest is this iskindof() function, which you claim to be using with success,
>> but give no detailed information of -- not even parameters.... have you found a reflection gem i'm
>> missing out on? :P

from ssnet, which was posted 07/14/2003 06:36PM PDT
My posting was also directed to ssnet, and was not an attempt
to solve the problem originally stated..

But enough about that. It seems that people are interrested in what is going on in here anyways.

recommendation: split points between fl0yd, BeyondWu, ssnet