Change VTBL of an existing object on the fly, dynamic subclassing

Consider the following setting.

Base class:

class Thing { int f1; int f2; Thing(NO_INIT) {} Thing(int n1 = 0, int n2 = 0): f1(n1),f2(n2) {} virtual ~Thing() {} virtual void doAction1() {} virtual const char* type_name() { return "Thing"; } } 

And derived classes that differ only in the implementation of the above methods:

 class Summator { Summator(NO_INIT):Thing(NO_INIT) {} virtual void doAction1() override { f1 += f2; } virtual const char* type_name() override { return "Summator"; } } class Substractor { Substractor(NO_INIT):Thing(NO_INIT) {} virtual void doAction1() override { f1 -= f2; } virtual const char* type_name() override { return "Substractor"; } } 

The task that I require requires the ability to change the class (VTBL in this case) of existing objects on the fly. This is called a dynamic subclass if I'm not mistaken.

So, I came up with the following function:

 // marker used in inplace CTORs struct NO_INIT {}; template <typename TO_T> inline TO_T* turn_thing_to(Thing* p) { return ::new(p) TO_T(NO_INIT()); } 

which does just that - it uses inplace new to create one object instead of another. Effectively, it simply changes the vtbl pointer in objects. Thus, this code works as expected:

 Thing* thing = new Thing(); cout << thing->type_name() << endl; // "Thing" turn_thing_to<Summator>(thing); cout << thing->type_name() << endl; // "Summator" turn_thing_to<Substractor>(thing); cout << thing->type_name() << endl; // "Substractor" 

The only serious problems I encounter are that a) each derived class must have special constructors, such as Thing(NO_INIT) {} , which should not do anything. And b) if I want to add elements such as std :: string to Thing, they will not work - only types that have NO_INIT constructors are allowed as Thing members.

Question: is there a better solution for such a dynamic subclass that solves problems "a" and "b"? I have a feeling that std :: move semantic might help solve "b" somehow, but I'm not sure.

Here is the ideone code.

+6
source share
8 answers

(Already answered on RSDN http://rsdn.ru/forum/cpp/5437990.1 )

There is a complicated way:

 struct Base { int x, y, z; Base(int i) : x(i), y(i+i), z(i*i) {} virtual void whoami() { printf("%p base %d %d %d\n", this, x, y, z); } }; struct Derived : Base { Derived(Base&& b) : Base(b) {} virtual void whoami() { printf("%p derived %d %d %d\n", this, x, y, z); } }; int main() { Base b(3); Base* p = &b; b.whoami(); p->whoami(); assert(sizeof(Base)==sizeof(Derived)); Base t(std::move(b)); Derived* d = new(&b)Derived(std::move(t)); printf("-----\n"); b.whoami(); // the compiler still believes it is Base, and calls Base::whoami p->whoami(); // here it calls virtual function, that is, Derived::whoami d->whoami(); }; 

Of course, this is UB.

+1
source

For your code, I'm not 100% sure according to the standard.

I think using a new placement that does not initialize any member variables, so to preserve the previous state of the class, this behavior is undefined in C ++. Imagine there is a new debugging arrangement that initializes the entire uninitialized member variable to 0xCC.


union is the best solution in this case. However , it looks like you are implementing a strategic template. If so, use a strategy template that will make the code much easier to understand and maintain.

Note: when using union , virtual should be removed.
Adding it is poorly formed, as mentioned by Mehrdad, since the introduction of a virtual function does not correspond to standard layouts.

example

 #include <iostream> #include <string> using namespace std; class Thing { int a; public: Thing(int v = 0): a (v) {} const char * type_name(){ return "Thing"; } int value() { return a; } }; class OtherThing : public Thing { public: OtherThing(int v): Thing(v) {} const char * type_name() { return "Other Thing"; } }; union Something { Something(int v) : t(v) {} Thing t; OtherThing ot; }; int main() { Something sth{42}; std::cout << sth.t.type_name() << "\n"; std::cout << sth.t.value() << "\n"; std::cout << sth.ot.type_name() << "\n"; std::cout << sth.ot.value() << "\n"; return 0; } 

As mentioned in the standard:

In a union, no more than one non-static data element can be active at any time, that is, the value of no more than one of the non-static data elements can be stored in the union at any time. [Note. To simplify the use of joins, there is one special guarantee: if a standard layout union contains several standard layout structures that have a common initial sequence (9.2), and if an object of this standard layout type contains one of the standard layout structures, it is allowed to check the general initial sequence of any of the elements standard layout structures; see 9.2. - final note]

+1
source

Question: is there a better solution for such a dynamic subclass that solves problems "a" and "b"?

If you have a fixed set of subclasses, you can use an algebraic data type like boost::variant . Keep shared data separate and put all the different parts in an option.

Properties of this approach:

  • naturally works with a fixed set of "subclasses". (although some type of erasable class can be placed in the option, and the set will become open).
  • Sending is done using the switch on the small integral tag. Tag size can be minimized to one char . If your "subclasses" are empty, then there will be a small additional overhead (depending on alignment), because boost::variant does not perform empty-base optimization .
  • "Subclasses" may have arbitrary internal data. Such data from different "subclasses" will be placed in one aligned_storage .
  • You can do a bunch of operations with a “subclass” using only one mailing per package, while in the general case with the virtual or indirect call manager there will be per call. In addition, calling a method from within a “subclass” will not have indirectness, while in virtual calls you must play with the final keyword in order to try to achieve this.
  • self for basic shared data must be explicitly passed.

Ok, here is the proof of concept:

 struct ThingData { int f1; int f2; }; struct Summator { void doAction1(ThingData &self) { self.f1 += self.f2; } const char* type_name() { return "Summator"; } }; struct Substractor { void doAction1(ThingData &self) { self.f1 -= self.f2; } const char* type_name() { return "Substractor"; } }; using Thing = SubVariant<ThingData, Summator, Substractor>; int main() { auto test = [](auto &self, auto &sub) { sub.doAction1(self); cout << sub.type_name() << " " << self.f1 << " " << self.f2 << endl; }; Thing x = {{5, 7}, Summator{}}; apply(test, x); x.sub = Substractor{}; apply(test, x); cout << "size: " << sizeof(x.sub) << endl; } 

Output:

 Summator 12 7 Substractor 5 7 size: 2 

LIVE DEMO at Coliru

Full code (it uses some functions of C ++ 14, but can be mechanically converted to C ++ 11):

 #define BOOST_VARIANT_MINIMIZE_SIZE #include <boost/variant.hpp> #include <type_traits> #include <functional> #include <iostream> #include <utility> using namespace std; /****************************************************************/ // Boost.Variant requires result_type: template<typename T, typename F> struct ResultType { mutable F f; using result_type = T; template<typename ...Args> T operator()(Args&& ...args) const { return f(forward<Args>(args)...); } }; template<typename T, typename F> auto make_result_type(F &&f) { return ResultType<T, typename decay<F>::type>{forward<F>(f)}; } /****************************************************************/ // Proof-of-Concept template<typename Base, typename ...Ts> struct SubVariant { Base shared_data; boost::variant<Ts...> sub; template<typename Visitor> friend auto apply(Visitor visitor, SubVariant &operand) { using result_type = typename common_type < decltype( visitor(shared_data, declval<Ts&>()) )... >::type; return boost::apply_visitor(make_result_type<result_type>([&](auto &x) { return visitor(operand.shared_data, x); }), operand.sub); } }; /****************************************************************/ // Demo: struct ThingData { int f1; int f2; }; struct Summator { void doAction1(ThingData &self) { self.f1 += self.f2; } const char* type_name() { return "Summator"; } }; struct Substractor { void doAction1(ThingData &self) { self.f1 -= self.f2; } const char* type_name() { return "Substractor"; } }; using Thing = SubVariant<ThingData, Summator, Substractor>; int main() { auto test = [](auto &self, auto &sub) { sub.doAction1(self); cout << sub.type_name() << " " << self.f1 << " " << self.f2 << endl; }; Thing x = {{5, 7}, Summator{}}; apply(test, x); x.sub = Substractor{}; apply(test, x); cout << "size: " << sizeof(x.sub) << endl; } 
+1
source

use return new(p) static_cast<TO_T&&>(*p);

Here is a good resource regarding movement semantics: What is movement semantics?

0
source

You simply cannot legally "modify" an object class in C ++.

However, if you indicate why you need it, we can offer alternatives. I can think of it:

  • Make v-tables "manual". In other words, each object of this class must have a pointer to a table of function pointers that describes the behavior of the class. To change the behavior of this class of objects, you change function pointers. Pretty painful, but what is the whole point of v-tables: distract this from you.

  • Use discriminated associations ( variant , etc.) to place objects of potentially different types inside the same object. I am not sure if this is the right approach for you.

  • Do something specific to implement. You can probably find v-table formats on the Internet for any implementation you use, but you are entering the undefined behavior area here to play with fire. And this, most likely, will not work on another compiler.

0
source

You should be able to reuse data by separating them from the Thing class. Something like that:

 template <class TData, class TBehaviourBase> class StateStorageable { struct StateStorage { typedef typename std::aligned_storage<sizeof(TData), alignof(TData)>::type DataStorage; DataStorage data_storage; typedef typename std::aligned_storage<sizeof(TBehaviourBase), alignof(TBehaviourBase)>::type BehaviourStorage; BehaviourStorage behaviour_storage; static constexpr TData *data(TBehaviourBase * behaviour) { return reinterpret_cast<TData *>( reinterpret_cast<char *>(behaviour) - (offsetof(StateStorage, behaviour_storage) - offsetof(StateStorage, data_storage))); } }; public: template <class ...Args> static TBehaviourBase * create(Args&&... args) { auto storage = ::new StateStorage; ::new(&storage->data_storage) TData(std::forward<Args>(args)...); return ::new(&storage->behaviour_storage) TBehaviourBase; } static void destroy(TBehaviourBase * behaviour) { auto storage = reinterpret_cast<StateStorage *>( reinterpret_cast<char *>(behaviour) - offsetof(StateStorage, behaviour_storage)); ::delete storage; } protected: StateStorageable() = default; inline TData *data() { return StateStorage::data(static_cast<TBehaviourBase *>(this)); } }; struct Data { int a; }; class Thing : public StateStorageable<Data, Thing> { public: virtual const char * type_name(){ return "Thing"; } virtual int value() { return data()->a; } };
template <class TData, class TBehaviourBase> class StateStorageable { struct StateStorage { typedef typename std::aligned_storage<sizeof(TData), alignof(TData)>::type DataStorage; DataStorage data_storage; typedef typename std::aligned_storage<sizeof(TBehaviourBase), alignof(TBehaviourBase)>::type BehaviourStorage; BehaviourStorage behaviour_storage; static constexpr TData *data(TBehaviourBase * behaviour) { return reinterpret_cast<TData *>( reinterpret_cast<char *>(behaviour) - (offsetof(StateStorage, behaviour_storage) - offsetof(StateStorage, data_storage))); } }; public: template <class ...Args> static TBehaviourBase * create(Args&&... args) { auto storage = ::new StateStorage; ::new(&storage->data_storage) TData(std::forward<Args>(args)...); return ::new(&storage->behaviour_storage) TBehaviourBase; } static void destroy(TBehaviourBase * behaviour) { auto storage = reinterpret_cast<StateStorage *>( reinterpret_cast<char *>(behaviour) - offsetof(StateStorage, behaviour_storage)); ::delete storage; } protected: StateStorageable() = default; inline TData *data() { return StateStorage::data(static_cast<TBehaviourBase *>(this)); } }; struct Data { int a; }; class Thing : public StateStorageable<Data, Thing> { public: virtual const char * type_name(){ return "Thing"; } virtual int value() { return data()->a; } }; 

It is guaranteed that the data will be saved unchanged when you change Thing to a different type, and the offsets should be calculated at compile time, so performance should not be affected.

With the static_assert property static_assert you need to be sure that all offsets are correct, and there is enough storage space to store your types. Now you need to change the way you create and destroy Thing s.

 int main() { Thing * thing = Thing::create(Data{42}); std::cout << thing->type_name() << "\n"; std::cout << thing->value() << "\n"; turn_thing_to<OtherThing>(thing); std::cout << thing->type_name() << "\n"; std::cout << thing->value() << "\n"; Thing::destroy(thing); return 0; }
int main() { Thing * thing = Thing::create(Data{42}); std::cout << thing->type_name() << "\n"; std::cout << thing->value() << "\n"; turn_thing_to<OtherThing>(thing); std::cout << thing->type_name() << "\n"; std::cout << thing->value() << "\n"; Thing::destroy(thing); return 0; } 

UB still exists due to non-reassignment of Thing , which can be fixed using the result of turn_thing_to

 int main() { ... thing = turn_thing_to<OtherThing>(thing); ... }
int main() { ... thing = turn_thing_to<OtherThing>(thing); ... } 
0
source

Here is another solution

While it is slightly less optimal (it uses intermediate storage and CPU cycles to call moving ctors), it does not change the semantics of the original task.

 #include <iostream> #include <string> #include <memory> using namespace std; struct A { int x; std::string y; A(int x, std::string y) : x(x), y(y) {} A(A&& a) : x(std::move(ax)), y(std::move(ay)) {} virtual const char* who() const { return "A"; } void show() const { std::cout << (void const*)this << " " << who() << " " << x << " [" << y << "]" << std::endl; } }; struct B : A { virtual const char* who() const { return "B"; } B(A&& a) : A(std::move(a)) {} }; template<class TO_T> inline TO_T* turn_A_to(A* a) { A temp(std::move(*a)); a->~A(); return new(a) B(std::move(temp)); } int main() { A* pa = new A(123, "text"); pa->show(); // 0xbfbefa58 A 123 [text] turn_A_to<B>(pa); pa->show(); // 0xbfbefa58 B 123 [text] } 

and ideone .

The decision is based on the idea expressed below by Nikolai Merkin. But he suspects UB somewhere in turn_A_to<>() .

0
source

I have the same problem, and although I do not use it, one of the solutions that I was thinking about is to have one class and enable methods depending on the element number in the class. Changing a type is as simple as changing a type number.

 class OneClass { int iType; const char* Wears() { switch ( iType ) { case ClarkKent: return "glasses"; case Superman: return "cape"; } } } : : OneClass person; person.iType = ClarkKent; printf( "now wearing %s\n", person.Wears() ); person.iType = Superman; printf( "now wearing %s\n", person.Wears() ); 
0
source

All Articles