Container Polymorphism STL STL

I was wondering if there is an elegant solution to this problem.

Assume the following:

class Base { private: int data; protected: Base(int data) : data(data) { } int getData() const { return data; } virtual bool someOp() = 0; } class Derived1 : public Base { private: int data2; public: Derived1(int data, int data2) : Base(data), data2(data2) { } int getData2() const { return data2; } bool someOp() override { /* something */ } } class Derived2 : public Base { private: float data2; public: Derived1(int data, float data2) : Base(data), data2(data2) { } float getData2() const { return data2; } bool someOp() override { /* something */ } } 

And suppose I have full control over the hierarchy, so I can assume that Base will not be extended, nor the DerivedX class.

Now I want to save them in std::vector , if I want to use polymorphism, I have to store pointers, otherwise overlaying objects will not allow me to store additional derived properties. Therefore, I am basically forced to use std::vector<unique_ptr<Base>> .

But let me suppose that I need to store a lot of such objects, and I do not want to spend money on allocating a double heap (internal std::vector + the object itself) and at the same time I can assume that:

  • class hierarchy is well defined and will not be expanded without its knowledge
  • sizeof(DerivedX) will not be larger than sizeof(Base)

So, I am wondering if there is an elegant way to preserve polymorphism and avoid storing pointers. I might think of some solutions like

 class Base { enum { DERIVED1, DERIVED2 } type; int data; union { int derived1data; float derived2data; } bool someOp() { if (this->type == DERIVED1) ... else if .. } } 

But this is clearly not elegant. I could also try to use the fact that fragmentation of objects should not occur if sizeof(Derived) == sizeof(Base) using a secure union in Base and accessing it from Derived and dropping the address into std::vector elements to the desired type (according to the listing), but it also sounds ugly.

+5
source share
2 answers

Erase styles and optimize a small buffer.

You can erase almost any property in C ++ by creating a custom interface that β€œknows” how to apply the property to a type that is currently unknown.

boost::any type erases to copy, destroy, get typeid and type cast-back-to-exact-matching-type. std::function type erases before copying, is called with a certain signature, destruction and call back to the identical type (the latter is rarely used).

Free implementations of storage-based type erasure get the semantics of the move β€œfor free,” replacing pointers.

In your case, you need to create a "fairly large" aligned repository in the type. You want to type erase to copy, get-as-a-link-to-base, destroy, and probably move (since you store internally).

std::aligned_storage is for your task (you can pass alignment requirements for all types that you are going to store). Then a new object is in place.

Create a table of operations that you want to perform on the object via void* - copy, move, destroy and convert-to- base* .

 template<class Sig>using f = Sig*; struct table { f<base*(void*)> convert_to_base; f<base const*(void const*)> convert_to_const_base; f<void(void*,void const*)> copy_construct; f<void(void*)> destroy; f<void(void*,void*)> move_construct; }; template<class T> table const* get_table() { static table t = { // convert to base: [](void*p)->base*{ T*t=static_cast<T*>(p); return t; }, // convert to const base: [](void const*p)->base const*{ T const*t=static_cast<T const*>(p); return t; }, // etc }; return &t; } 

now save get_table<T>() in your instance with type-erase (basically it is a virtual function table, manually implemented) and write your usual traversal class to use the functions from table to control aligned_storage<?...> .

Now it can be done easier by using boost::variant or through something like my some type, which acts like any without using heap storage. Some link includes an implementation that compiles the above pseudo-virtual function table technique. I probably misused leveled storage incorrectly, so be careful.

+4
source

You can use std :: aligned_storage to transfer your classes. Assuming Derived2 is the largest:

 class Storage { public: Storage(int data, int data2) { new (&data) Derived1(data, data2); } Storage(int data, float data2) { new (&data) Derived2(data, data2); } Base* getBase() { return reinterpret_cast<Base*>(&data); } ~Storage() { getBase()->Base::~Base(); } private: std::aligned_storage<sizeof(Derived2)> data; }; 
+2
source

Source: https://habr.com/ru/post/1214591/


All Articles