[comp.lang.c++] Question: Object transformations in C++

jrb@druco.ATT.COM (John Behrs) (06/21/89)

How can an instantiation of one derived class transform into a
different derived class of the same base class?  The derived classes
have different virtual functions but have no additional private data,
so what I really would like to do is just change the "virtual" type field.
Below is some code that I hope illustrates the problem.

//
// Class transformations
//
#include <stream.h>

class base {
	int base_a;
	int base_b;
public:
	base(int a, int b) { base_a = a; base_b = b; }
	int a() { return base_a; }
	int b() { return base_b; }
	virtual void func() {}
};

class x:public base {
public:
	x(int a, int b) : (a, b) {}
	void func() { cout << "X"; }
};

class y:public base {
public:
	y(int a, int b) : (a, b) {}
	void func() { cout << "Y"; }
};

main()
{
	class x x1(5,10);
	x1.func();	// prints 'X'
//
//	Assuming class x and class y to have identical data structure,
//	convert x1 to be of class y
//
//	(I'd much rather change just the virtual type field,
//	but I don't know how.)
//
	{
	class y y1(x1.a(), x1.b());
	x1 = *(class x *)&y1;
	}
//
//	x1 is now of class y
//
	x1.func();	// prints 'Y'
}
-- 
"What's the point of being alive,           | John Behrs
if you're not going to communicate?"        | druco!jrb
-- Kurt Vonnegut, Bluebeard.                | (303) 538-3539

jima@hplsla.HP.COM (Jim Adcock) (06/24/89)

//	(I'd much rather change just the virtual type field,
//	but I don't know how.)

I think I'd discourage such an approach.  Many C++ compilers optimize away
some of the virtual function dispatch garbage if they unambigously "know"
the type of an object at compile time.  If you play dirty tricks to change
the virtual table pointer, then virtual functions that get called from
this object in the future, using the full dispatch protocol, will think
your new object is indeed the new type, whereas virtual functions that
the compiler determines [based on an unambiguous knowledge of type] don't
need the full dispatch protocol will be of the old type.

So you'd be making a scitzoid[sp] object.

You might be able to do something like this:


#include <stdio.h>

class base
{
  int somecommonstuff;
public:
  virtual char* classname(){return (char*)"base";}
  void printclass(){printf("object@%x is a %s\n",(unsigned)this,classname());}
};

class X : public base
{
public:
  virtual char* classname(){return (char*)"X";}
};

class Y : public base
{
public:
  virtual char* classname(){return (char*)"Y";}
  Y(X& x){this = (Y*)(&x);}
};

main()
{
   X x;
   x.printclass();
   Y* yp = new Y(x);
   yp->printclass();
   x.printclass(); // depends on the compiler
}


But this is still bad news for several reasons.  1) Assignment to this is 
a "feature" which is going obsolete and won't be supported on better optimizing
compilers in the future.  Perhaps something similar can be done using 
overloaded new, but I think it will still be a hack. 2)  If you don't use
the <new Y(x)> protocol, but rather use Y(X&) to intialize a static or a 
local, your program will probably [rightly] bomb, since a different object
address has already been assigned by the compiler. 3) Again, the results of
the final x.printclass() is going to depend on the compiler.

My advice is to find a different approach to what you're trying to do.
Is your idea based on experiences with other OOP languages?  If so, please
realize experience with other OOPLs is not necessarily an advantage when
using C++.

trm@cbnews.ATT.COM (Tom R. Mueller) (06/26/89)

Here's a portable way to change the vtbl entry.  This example transforms 
an object from a base class to a derived class.  It uses forwarding
functions in the base class to redirect calls through the vtbl.  Note that
if you're calling functions in a derived class using d.func(), the
vtbl isn't referenced so changing it won't help.

#include <stream.h>

class Base
{
public:
        Base() { cout << "Constructing Base.\n"; }
        void makeMeADerived();
        virtual void f()
        {
	                cout << "Called Base::f()\n";
	                this->f();
        }
        void *operator new(long) { return ::new Base; };
        void *operator new(long, void *where)
        {
                return where;
        }
        void operator delete(void *) { }
};

class Derived : public Base
{
public:
        Derived() { cout << "Constructing Derived.\n"; }
        void f() { cout << "Called Derived::f()\n"; }
};

void Base::makeMeADerived()
{
        Derived *d = new(this) Derived;
}

main()
{
        Base b;

        b.makeMeADerived();
        b.f();                  // Calls Derived::f (indirectly via Base)
        (&b)->f();              // Calls Derived::f (directly via vtbl)
}

-- 
- Tom Mueller			AT&T Bell Laboratories (CB 0D109)
 (614) 860-5287			6200 East Broad Street
  ...att!cblpn!trm		Columbus, OH  43213

jima@hplsla.HP.COM (Jim Adcock) (06/29/89)

> / hplsla:comp.lang.c++ / john@hhb.UUCP (John Sissler) /  7:21 am  Jun 24, 1989 /
> > How can an instantiation of one derived class transform into a
> > different derived class of the same base class?  The derived classes
> > have different virtual functions but have no additional private data,
> > so what I really would like to do is just change the "virtual" type field.
> > Below is some code that I hope illustrates the problem.
> 
> 	I have had the same desire to change the virtual table in order to
> 	transform instances, and I suppose this is my opportunity to
> 	come out of the closet.
> 
> 	My scenario is essentially this: during initialization of a 
> 	performance-critical application, a graph of instances is created. 
> 	After the graph is complete, each node is given a chance to 
> 	optimize itself based on information which is available only 
> 	after the graph is complete.  Each node is constrained to occupy 
> 	the same space.  Due to performance and memory requirements, 
> 	indirection (i.e. instance handles / instance pointer table) is 
> 	not an alternative.  Thus, one means by which the instance can 
> 	optimize itself is to modify its virtual table to acquire a set 
> 	of "fast" methods.
> 	
> 	A cfront-dependent method of achieving this is
> 	to simply modify the vptr field, for example:
> 
> 	void Derived::Transform(const AnotherDerived& newInstance) {
> 		_vptr = newInstance._vptr;
> 	}
> 
> 	Admittedly, I have only experimented with this technique, and
> 	I doubt whether I'll actually use it in production code unless
> 	it gets language support (which I doubt it will or should).  

The example I gave before using initializers to change a vtable pointer
of an existing object will work in this example -- though you'd still 
probably want to do it by overloading new vs the "this assignment" I
used.  If you only change an object derived from some base class with
virtual functions, only use those virtual functions with the changed
object, the changed object being one of several different derived classes,
and only refer to the object using base class pointers, then this trick
should be pretty safe.  The point is that you must not allow a compiler
to "figure out" an object's "true type" at compile time -- since you've
changed the run-time portion of an object's type -- the vtable pointer --
behind the compiler's back.

These techniques still have to be considered "dirty pool" that may get
you in trouble in the future as compilers and the language progress.

jima@hplsla.HP.COM (Jim Adcock) (06/30/89)

Regards trm's comments --

The example shows what I was thinking of in terms of using new, otherwise
the same as my examples using "this."

But, I think maybe he's wrong about this technique being safe -- at least
it confused the hell out of my compiler producing:


Constructing Base.
Constructing Base.
Constructing Derived.
Called Base::f()
Called Derived::f()
Called Derived::f()

-- which is surely wrong by any standards.

I would expect b.f() to be ill-defined, compiler dependent, depending on
whether a compiler chooses to evaluate this at compile time or run
time.  For this to work as trm says would seem to imply virtual functions
*always* have to be evaluated at run time -- which would also prevent
their ever being in-line substituted.

I think this would be wrong -- making people who don't play hack games
with objects pay a substantial performance penelty for people who do play
hack games with objects!

Can we have this language issue clarified, please?

jima@hplsla.HP.COM (Jim Adcock) (07/01/89)

//Here's a little cleaner approach based on a combo of previous listings that
//might be reasonable.  The idea is to match the compile-time type of the
//object to the run-time type of the object, using references.  So now you
//have a new object "d" that occupies the space previously held by "b",
//the vtable pointer of "b" having been overwritten as a result of 
//Derived::new(long, Base&).  Presumably values of instance variables of
//b will be retained for d to use.  Of course if one makes the mistake of
//accessing b after it has been cannibalized by d, you'd still be in deep
//xxxx trouble.

//whether this is actually going to be fast enough to meet your goals depends
//on your goals, and your compiler.


#include <stream.h>

class Derived;

class Base
{
public:
    Base() { cout << "Constructing Base.\n"; }
    virtual void f()
    {
        cout << "Called Base::f()\n";
    }
};

class Derived : public Base
{
public:
    Derived() { cout << "Constructing Derived.\n"; }
    void f()  { cout << "Called Derived::f()\n"; }
    void * operator new(long) { return ::new Derived; }
    void * operator new(long, Base& b) { return &b; }
    void operator delete(void *) { }
};

main()
{
    Base b;
    b.f();
    cout << "\n";

    Derived& d = *(new(b) Derived);
    cout << "\n";

    d.f();               // How this gets called depends on the compiler?
    ((Base *)(&d))->f(); // whereas this get called via vtable?
}