TITLE: changing an object's type (Newsgroups: comp.std.c++, 25 Nov 96) HUGHES: Graham Hughes >In the paper `Sixteen Ways to Stack a Cat', Stroustrup describes a >supposedly portable method of nuking the vtbl of an object and replacing >it at runtime. To wit: > >Suppose you have a root class foo, that looks like this: > >class foo { > ... general nonsense ... > class unused {}; > foo(unused) {} >}; > >and you want to change the virtual functions to those of the class ffoo, >which also looks like > >class ffoo : public foo { > ... general nonsense (but no data members: important) ... > ffoo(foo::unused) {} >}; > >You have an object of class foo, and you want to make it an ffoo. The >paper suggests > >void bar (foo & f) >{ > foo::unused u; > new(&f) ffoo(u); >} > >using the placement new operator. > >Does the DWP break this in any way? I ask because someone has claimed >that it does, and I don't know enough to confirm or deny. > >(Note: I avoid any discussion of whether it *should* break this) CLAMAGE: clamage@taumet.eng.sun.com (Steve Clamage) I don't think "break" is the appropriate word. That implies a rule change that makes formerly valid code invalid, and I don't think that is the case here. The DWP does not assure us that the trick above will work, but I don't think you could find any previous language rules that said it was valid, either. The results before and after the DWP I would say are undefined. That is not necessarily the same as "unportable". The derived class adds no data, but only new functions. An implicit assumption is that the derived class is the same size as the base class, and the layout is also the same. We are not assured that is the case, but as a practical matter it probably is. Another implicit assumption, which is at least as safe, is that the derived destructor does nothing more than call the base destructor. (That is, the changed object, now a "derived", can be destroyed correctly by calling the "base" destructor.) Another subtle point: The placement-new operation will cause the (empty) special constructors to run, which will default-initialize the base-class data. If each data member has a no-op default constructor, its value should remain the same. If a data member does not have a default constructor, the code won't compile. If a data member has a nontrivial default constructor, it will be run without the data member having been destroyed (possibly causing resource leaks), and the value of the data member might change. All in all, this is not a general technique, and is not directly supported as far as I know by any version of C++ language rules. Nonetheless, it might well be portable in restricted circumstances. The DWP does assure us that we can destroy an object and create a new object of the SAME type in its place via a placement-new operation. Example: void f() { T t(1, 2); t.~T(); // destroy T object new (&t) T(3, 4); // make a new T object in place // ... use t; } // t automatically destroyed at exit