Re: Design question: polymorphism after object creation
On Mar 29, 2:34 am, Marcel M=FCller <news.5.ma...@spamgourmet.com>
wrote:
A union with a few types and a stable common base class would be nice.
If the base is virtual, the memory layout could match the requirements
in theory - but only in theory.
Like this:
+------------------------+
| Base |
+-------+--------+-------+
| File | Folder | Other |
+-------+ | |
+ free | +-------+
+-------+--------+-------+
This could turn into 4 different classes (if Base is not abstract)
without the need to change the Pointer to Base.
Just out of curiosity, here is what I came up with:
------------------ polymorph.h ------------------
#include <cstddef>
#include <type_traits> // or <boost\tr1\type_traits.hpp>
using std::tr1::aligned_storage;
using std::tr1::alignment_of;
template <typename T1, typename T2>
struct max_size
{
static const std::size_t value = sizeof (T1) > sizeof (T2) ? sizeof
(T1) : sizeof (T2);
};
template <class base, class derived1, class derived2>
class polymorph
{
//static_assert (std::has_virtual_destructor<base>::value);
//static_assert (std::is_base_of<base, derived1>::value);
//static_assert (std::is_base_of<base, derived2>::value);
public:
polymorph() : object_ptr (reinterpret_cast<base*> (&object_space))
{
new ((void*)object_ptr) base;
}
~polymorph()
{
object_ptr->~base();
}
base* operator -> () { return object_ptr; }
template <class T>
void change_type()
{
//base temp (std::move (*object_ptr));
object_ptr->~base();
object_ptr = const_cast<base* volatile> (object_ptr);
new ((void*)object_ptr) T; // (std::move (temp));
}
private:
// Even though object_ptr always points to object_space,
// it is needed to provide memory barrier when object type changes
// (invalidate compiler's cache of object's const members including
vptr).
base* object_ptr;
typename aligned_storage<max_size<derived1, derived2>::value,
alignment_of<base>::value>::type object_space;
//char object_space /*[[align(base)]]*/ [max_size<derived1,
derived2>::value];
};
------------------ polymorph.h ------------------
And the test code:
------------------ polymorph_test.cpp -----------
#include <iostream>
using std::cout;
#include "polymorph.h"
struct base
{
base() { cout << "base()\n"; }
virtual ~base() { cout << "~base()\n"; }
virtual void func() { cout << "base::func()\n"; }
};
struct derived1 : base
{
derived1() { cout << "derived1()\n"; }
~derived1() { cout << "~derived1()\n"; }
virtual void func() { cout << "derived1::func()\n"; }
};
struct derived2 : base
{
derived2() { cout << "derived2()\n"; }
~derived2() { cout << "~derived2()\n"; }
virtual void func() { cout << "derived2::func()\n"; }
};
typedef polymorph<base, derived1, derived2> object_type;
void main()
{
object_type obj;
obj->func();
obj.change_type<derived1>();
obj->func();
obj.change_type<derived2>();
obj->func();
}
------------------ polymorph_test.cpp -----------
Here is the output:
base()
base::func()
~base()
base()
derived1()
derived1::func()
~derived1()
~base()
base()
derived2()
derived2::func()
~derived2()
~base()
When type is changed object is actually destroyed and re-created, so
you need some way to transfer state. But all references remain valid.
This implementation does provide some performance benefits compared to
standard proxy approach, but unless you really need those, I don't
know if I would use something like this in production code.
Boris.