6

Consider the following setup.

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 are different only by implementation of methods above:

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 I have requires ability to change class (VTBL in this case) of existing objects on the fly. This is known as dynamic subclassing if I am 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()); 
    }

that does just that - it uses inplace new to construct one object in place of another. Effectively this just changes vtbl pointer in objects. So 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 major problems I have with this approach is that a) each derived classes shall have special constructors like Thing(NO_INIT) {} that shall do precisely nothing. And b) if I will want to add members like std::string to the Thing they will not work - only types that have NO_INIT constructors by themselves are allowed as members of the Thing.

Question: is there a better solution for such dynamic subclassing that solves 'a' and 'b' problems ? I have a feeling that std::move semantic may help to solve 'b' somehow but not sure.

Here is the ideone of the code.

c-smile
  • 26,734
  • 7
  • 59
  • 86
  • 1
    What do you need this for? If you described the actual problem at hand a better solution could be suggested than invoking unspecified behavior and risking the wrath of nasal demons. – Anton Tykhyy Jan 21 '14 at 11:21
  • @AntonTykhyy: consider tree of HTML DOM elements (class element). After parsing and later in runtime each DOM element may get different layout models defined by CSS/styles, script, etc. So each element is allowed to be of class block_element, table_element, etc - with different layout and visibility rules. Having such dynamic subclassing is essential to architecture that I use in my Sciter engine : http://terrainformatica.com/sciter/ – c-smile Jan 21 '14 at 22:45
  • Then change the architecture. If you want to change elements' behavior at runtime, separate behavior from data and assign pointers to behavior objects. The only thing you are saving using this sort of undefined behavior is one pointer access, and one very likely to hit the cache, at that. – Anton Tykhyy Jan 21 '14 at 23:04
  • @antonTykhyy Additional level of indirection is one drawback, another one is that use of strategy or pimpl pattern increases maintenance/development cost. Root class has around 50 methods that need to be bridged to current strategy methods. Not that pretty at the end. And it's not clear why to introduce new entities if C++ already has builtin infrastructure. Virtuality is a strategy implementation if to think slightly out of the box. – c-smile Jan 22 '14 at 00:07
  • > The only thing you are saving ... is one pointer access, and one very likely to hit the cache You say that like it's a bad thing, @AntonTykhyy... I hit on the similar solution as c-smile for the same problem. I'm making real-time audio synthesis software and have an array of digital filters. I'd like to let the user change filters on the fly. Real-time disallows heap. Placement operator indicated as I start using new the new filter the exact time I stop using the old one. Structs same, virtual methods same. Either do this or make one class with switch's! And why is it undefined? – Swiss Frank Jul 08 '16 at 03:09
  • @c-smile, what about simply having a no-argument constructor, and instead an Initialize() method? – Swiss Frank Jul 10 '16 at 06:04
  • @SwissFrank usually no-argument constructors are used for setting objects in some initial state. But that precisely what I want to avoid in `thing::thing(NO_INIT)` - to disable any state modification. – c-smile Jul 10 '16 at 16:36

8 Answers8

2

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

There is a tricky 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, it's UB.

Nickolay Merkin
  • 2,673
  • 1
  • 16
  • 14
  • 1
    Here it is but slightly reformulated: http://ideone.com/5O6SFV Where exactly you see UB there? – c-smile Jan 21 '14 at 22:29
  • @c-smile: Using `b` to access a new object occupying the same location, when the types don't match. – Ben Voigt Jun 22 '14 at 18:16
  • Not sure I understand your statement. Are you saying that use of inplace new in C++ is UB ? Or is it about reusing same memory range for different purposes in C++ ? – c-smile Sep 16 '17 at 17:53
1

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

I think the usage of the placement new which doesn't initialize any member variables, so to preserve previous class state, is undefined behavior in C++. Imagine there is a debug placement new which will initialize all uninitialized member variable into 0xCC.


union is a better solution in this case. However, it does seem that you are implementing the strategy pattern. If so, please use the strategy pattern, which will make code a lot easier to understand & maintain.

Note: the virtual should be removed when using union.
Adding it is ill-formed as mentioned by Mehrdad, because introducing virtual function doesn't meet standard layout.

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, at most one of the non-static data members can be active at any time, that is, the value of at most one of the non-static data members can be stored in a union at any time. [ Note: One special guarantee is made in order to simplify the use of unions: If a standard-layout union contains several standard-layout structs that share a common initial sequence (9.2), and if an object of this standard-layout union type contains one of the standard-layout structs, it is permitted to inspect the common initial sequence of any of standard-layout struct members; see 9.2. — end note ]

Xin
  • 1,300
  • 2
  • 14
  • 27
  • "usage of the placement new which doesn't initialize any member variables, so to preserve previous class state, is undefined behavior in C++." Where did you get that? new operator in C++ does not initialize any members. By its definition. It is a business of constructor to do that. – c-smile Jan 19 '14 at 05:57
  • As of strategy pattern... It is an implementation of strategy pattern where objects can change strategies dynamically. – c-smile Jan 19 '14 at 06:01
  • 1
    @c-smile It's the constructor's responsibility to initialize member variable, but it is not guaranteed that memory would not be changed after allocation. For non-placement new, it's common that debug `new` would fill memory with some fixed value for troubleshooting; but for placement new I'm not sure if the standard specifies memory must stay unchanged(not found in the standard). But I highly suspect it's not a well-defined behavior as in your code. – Xin Jan 19 '14 at 06:08
  • @c-smile Besides, to implement strategy pattern using the C++ union as in my code or your code, are both obscure, error-prone, and difficult to maintain. What if later 2 different fields are going to be added to different sub-class? The memory layout must be carefully maintained so not to break the code. Then using a pointer(unique_ptr/shared_ptr) seems to be a better choice. – Xin Jan 19 '14 at 06:12
  • Your solution, while it (probably) solves some other problems, breaks initial functionality at the same time. Your solution requires explicit calls like sth.ot.value() and sth.oth.value(). Potentially it can be solved by creation of addition wrapper around that union of yours but it will increase maintenance cost and will add one level of indirection or if/switch, etc. – c-smile Jan 19 '14 at 18:41
1

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

If you have fixed set of sub-classes then you may consider using algebraic data type like boost::variant. Store shared data separately and place all varying parts into variant.

Properties of this approach:

  • naturally works with fixed set of "sub-classes". (though, some kind of type-erased class can be placed into variant and set would become open)
  • dispatch is done via switch on small integral tag. Sizeof tag can be minimized to one char. If your "sub-classes" are empty - then there will be small additional overhead (depends on alignment), because boost::variant does not perform empty-base-optimization.
  • "Sub-classes" can have arbitrary internal data. Such data from different "sub-classes" will be placed in one aligned_storage.
  • You can make bunch of operations with "sub-class" using only one dispatch per batch, while in general case with virtual or indirect calls dispatch will be per-call. Also, calling method from inside "sub-class" will not have indirection, while with virtual calls you should play with final keyword to try to achieve this.
  • self to base shared data should be passed explicitly.

Ok, here is 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 is:

Summator 12 7
Substractor 5 7
size: 2

LIVE DEMO on Coliru

Full Code (it uses some C++14 features, but can be mechanically converted into 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;
}
Evgeny Panasyuk
  • 9,076
  • 1
  • 33
  • 54
0

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

Here is a good resource regarding move semantics: What are move semantics?

Community
  • 1
  • 1
Gasim
  • 7,615
  • 14
  • 64
  • 131
  • As of that static assert, it is enough to declare turn_to as a method of Thing class itself: `template inline TO_T* Thing::turn_to() { return ::new(this) TO_T(NO_INIT()); }` so it will be called as `thing->turn_to()`. I am not sure I understand second part about parameters passing. How they will help? As you see when calling `::new(this) Summator(NO_INIT())` I am not calling any destructors. That's the whole point - new object gets constructed with ready to use fields. – c-smile Jan 19 '14 at 02:42
  • with `static_assert` you get rid of the `NO_INIT` struct completely. And about second one you said `if I will want to add members like std::string to the Thing they will not work`; so with that you can pass variables to the constructor with ease. like if Summator had a string you will be able to say `thing->turn_to("testString");` and it will pass it correctly. Check out the code I provided in the answer (http://ideone.com/W3PgMS). – Gasim Jan 19 '14 at 02:53
  • you seems like missed the idea of the approach. Imagine thatC++ object has this layout in memory: `struct { void* vtbl; int f1; int f2 }` So what we need to do is to change vtbl value to some other. And all this shall be made by official C++ means only, code shall be portable, etc. – c-smile Jan 19 '14 at 03:05
  • `vtbl = new SomeClass`? Also using `void*` is a bad idea. If I had `class Base {...}; class Derived : public Base {}`, I would use `Base * base = new Derived;` – Gasim Jan 19 '14 at 03:07
  • omg I now understood it now; just use move constructor for that (http://ideone.com/g1x6ng). I though you were asking something completely different. Even though this will give you the same result they are not the same, your code doesn't initialize the parameter while C++11 one moves it. – Gasim Jan 19 '14 at 03:18
  • You say you don't want to change the member variables, yet you are trying to change them. The only way you can do this is define your own move constructors. – Gasim Jan 19 '14 at 22:17
  • No, I do not change member variables after initial construction. That's the whole point: after the transformation member variables shall stay as they are before. Preferably with zero cost in runtime. In your last attempt you've changed semantic of moving constructor. It does copying instead. – c-smile Jan 19 '14 at 23:26
0

You simply can't legally "change" the class of an object in C++.

However if you mention why you need this, we might be able to suggest alternatives. I can think of these:

  1. Do v-tables "manually". In other words, each object of a given class should have a pointer to a table of function pointers that describes the behavior of the class. To modify the behavior of this class of objects, you modify the function pointers. Pretty painful, but that's the whole point of v-tables: to abstract this away from you.

  2. Use discriminated unions (variant, etc.) to nest objects of potentially different types inside the same kind of object. I'm not sure if this is the right approach for you though.

  3. Do something implementation-specific. You can probably find the v-table formats online for whatever implementation you're using, but you're stepping into the realm of undefined behavior here so you're playing with fire. And it most likely won't work on another compiler.

user541686
  • 205,094
  • 128
  • 528
  • 886
  • As of "can't legally "change" the class". My pardon but what exactly here : http://ideone.com/DAulzT is not "legal" ? – c-smile Jan 19 '14 at 05:53
  • @c-smile: It's illegal. You're doing something the language has prohibited, hence the outcome (if any) is completely undefined. – user541686 Jan 19 '14 at 06:00
  • 2
    Could you be more specific? What exactly is prohibited there? – c-smile Jan 19 '14 at 06:03
  • 1
    @c-smile: Classes with virtual functions are not "standard-layout". That means you can't portably predict what any of their layouts look in memory. The C++ standard does not have any notion of a "v-table", that's just an implementation detail. So constructing one object on top of another and expecting the v-table pointers to match up is wrong and undefined behavior. – user541686 Jan 19 '14 at 06:13
  • 1
    It is not about VTBL actually. Yes, potentially virtuality in C++ can be implemented in some other way. There is nothing about explicit VTBL location in my case. C++ kind of guaranties that two objects of class Thing will have its fields located in memory with the same offsetof's and so their fields will have same storage locations in memory. As soon as as you are not calling destructor and do not wipe out memory occupied by the object it is always in constructed state. So there is no "standard layout" but there is "stable layout" of classes and other classes derived from them. – c-smile Jan 19 '14 at 18:16
  • The only part which could be incorrect in the sample is use of `thing` pointer after `turn_thing_to`. However, it can be made valid if we assign result of `turn_thing_to` to the `thing`. – Konstantin Oznobihin Jan 21 '14 at 07:25
  • @c-smile: Does C++ really guarantee that two objects of the same class will have their fields in the same location? I'm not saying you're wrong but I'm not sure you're correct either. What part of the standard makes you think this is true? – user541686 Jan 21 '14 at 07:36
  • 1
    @c-smile: it is true that the layouts of an object of class `Thing` and of the `Thing` sub-object of a `DerivedThing` are the same, otherwise `DerivedThing*` could not be cast to `Thing*`. However, you are mistaken in believing that the layout of `DerivedThing` begins with the layout of `Thing`. It is often so in popular implementations, but the standard does not mandate this behavior (that's what Mehrdad is saying) and in some important cases, e.g. multiple or virtual inheritance, the layouts don't match and `static_cast (&derivedThing)` is not a nop. IMSMR 'D&E' discusses this. – Anton Tykhyy Jan 21 '14 at 11:18
  • @AntonTykhyy: I'm not even sure I buy the first sentence. Consider an implementation that stores the *offsets* to its fields *inside* the objects. It's a bit bizarre, but is this illegal? If it's legal, then it can be very well the case that two objects of the same most-derived type would have the *offsets* arranged in the same order, but the fields the offsets refer to might be in fact arranged in a different order. In fact I think there might be a way to make the offsets be arranged in a different order. I'm not sure this is prohibited by C++ for non-standard-layout classes, is it? – user541686 Jan 21 '14 at 15:42
  • @Mehrdad: Heh, you're right. It isn't even necessary to imagine an implementation storing offsets to fields, because virtual inheritance usually works that way already (suppose `Thing:virtual X` and `DerivedThing:virtual X,Thing`, what is the offset of `X`?) So the physical layout may be different. However, I feel there is a slightly different meaning of *layout* such that the layouts of all `Thing`s and all `DerivedThing::Thing`s are the same: somebody with a `Thing*` must be able to work with any of them in the same manner. This doesn't mean placement-newing as used here will work, though. – Anton Tykhyy Jan 21 '14 at 16:16
  • > some important cases, e.g. multiple or virtual inheritance, the layouts don't match OK, but in cases these techniques aren't needed (in fact I've never needed them in 30+ years C++), placement-new without constructor seems attractive. Please remember this is for VERY specific problems, where we know a HUGE amount about the classes in question. All having the same size is already a huge limitation; add your "no multi/no virtual" to the limitations. But in that case, are we good? – Swiss Frank Jul 08 '16 at 03:21
  • @SwissFrank: Nowhere in my answer do I say this is possible in C++ "as long as you're not doing multiple or virtual inheritance". I'm saying this is impossible in C++, period. You may be able to do it in an implementation-specific way, but that requires using compiler hacks beyond what standard C++ provides or allows. – user541686 Jul 08 '16 at 04:06
0

You should be able to reuse data by separating it from your Thing class. Something like this:


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; }
};

Data is guaranteed to be leaved intact when you change Thing to other type and offsets should be calculated at compile-time so performance shouldn't be affected.

With a propert set of static_assert's you should be able to ensure that all offsets are correct and there is enough storage for holding your types. Now you only need to change the way you create and destroy your Things.


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;
}

There is still UB because of not reassigning thing which can be fixed by using result of turn_thing_to


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

Konstantin Oznobihin
  • 5,234
  • 24
  • 31
0

Here is one more solution

While it slightly less optimal (uses intermediate storage and CPU cycles to invoke moving ctors) it does not change semantic of 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(a.x)), y(std::move(a.y)) {}

    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 its ideone.

The solution is derived from idea expressed by Nickolay Merkin below. But he suspect UB somewhere in turn_A_to<>().

c-smile
  • 26,734
  • 7
  • 59
  • 86
0

I have the same problem, and while I'm not using it, one solution I thought of is to have a single class and make the methods switches based on a "item type" number in the class. Changing type is as easy as changing the 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() );
Swiss Frank
  • 1,985
  • 15
  • 33