There is a nice feature in TIScript that allows to change class of already instantiated object.
Let’s say we have two classes:
class MyWidget : Behavior { ... } class MyWidgetReadonly : Behavior { ... }
that handle user interaction with some widget on the screen. This widget can operate in two distinct modes: normal and read-only.
These modes share the same widget state but have substantially different behavioral characteristic. So it makes sense to split these two sets of methods into two classes to avoid pollution of our code by the crowd of if(readonly) ... else ...
.
When needed we can switch class of the object like this:
var widget = new MyWidget(); .... if( needReadOnlyMode ) widget.prototype = MyWidgetReadonly; // changing class of the object. ...
Pretty much all dynamic languages allow to do such things. E.g. in Python you can modify obj.__bases__
.
Surprisingly you can do the same in C++ too and without any “hack”. We can use so called placement new operator to do the trick. Here is how.
Let’s define our class hierarchy as this:
class MyWidget { public: int data; // state variables here virtual void on_mouse() { printf("normal on_mouse, data=%d\n",data); } virtual void on_key() { printf("normal on_key\n"); } }; class MyWidgetReadOnly: public MyWidget { // no data at all here, sic! virtual void on_mouse() { printf("read-only on_mouse, data=%d\n",data); } virtual void on_key() { printf("read-only on_key\n"); } };
And we will switch class of our object using this helper template function:
// turn_to(A* obj) changes class of the object // that means it just replaces VTBL of the object by VTBL of another class. // NOTE: these two classes has to be ABI compatible! template <typename TO_T, typename FROM_T> inline void turn_to(FROM_T* p) { assert( sizeof(FROM_T) == sizeof(TO_T)); ::new(p) TO_T(); // use of placement new }
Note: This will work if our class has default constructor that does not initialize any member variables.
Let’s try this small sample ( test.cpp ):
#include <new> #include <assert.h> // turn_to(A* obj) changes class of the object // that means it just replaces VTBL of the object by VTBL of another class. // NOTE: these two classes has to be ABI compatible! template <typename TO_T, typename FROM_T> inline void turn_to(FROM_T* p) { assert( sizeof(FROM_T) == sizeof(TO_T)); ::new(p) TO_T(); // use of placement new } class MyWidget { public: int data; // state variables here virtual void on_mouse() { printf("normal on_mouse, data=%d\n",data); } virtual void on_key() { printf("normal on_key\n"); } }; class MyWidgetReadOnly: public MyWidget { // no data at all here, sic! virtual void on_mouse() { printf("read-only on_mouse, data=%d\n",data); } virtual void on_key() { printf("read-only on_key\n"); } }; int main(int argc, char* argv[]) { MyWidget *w = new MyWidget(); w->data = 123; w->on_mouse(); turn_to<MyWidgetReadOnly>(w); // turning the instance to MyWidgetReadOnly class. w->on_mouse(); return 0; }
After compiling and running it we should see in console output like this:
normal on_mouse, data=123 read-only on_mouse, data=123
So it works – we are able to transform existing instance of some class to the instance of other class.
DISCLAIMER:
Be warned that this is a Jedi Sword feature. Use it responsibly, only if you know why you need it. And what are the consequences. Even mentioning of this approach in some software development companies (that usually are on the Dark Side) may damage your reputation.
This is some genuinely bad advice.
There is no way to modify “just the VTBL” like you claim. The placement new will construct any non-POD instances that are within your object. It may also reset your POD values if your constructor is sane and initializes all members, like it should. So, what you propose will break as soon as your data is, say a std::string instead of an int. And break it will, in subtle ways — most likely leaking memory, and losing original object’s data.
The correct way of implementing turn_to would be as follows, assuming that TO_T can be constructed from FROM_T. In most GUI toolkits, I’d think Widgets are not copyable and can’t be constructed from other Widgets, so caveat emptor as far as Widgets go.
To Kuba Ober: That “The placement new will construct any non-POD instances that are within your object” statement of yours is not exactly true. placement new just writes new VTBL pointer. Change of data bits is a prerogative of constructors in C++.
The way that it can be used is close to this:
And turn_to looks like:
Key point here is to use special ctor that explicitly does nothing – is not changing underlying bits.
The article just shows basic idea how it could be done when really needed.