Home > Blockchain >  c Inherited instances in a union
c Inherited instances in a union

Time:12-21

I have this optimization problem where my program goes through a configuration phase then an execution phase. During configuration, an implementation of a function is selected, then this function is called at each loop iteration during execution.

I want to avoid going through switch/case at each loop to check which function to call. Solution is a function pointer. Now each of these function can have an internal state. A good solution would be inheritance where each class implements the "do_something()" function of the base class and the compiler makes a vtable and everything is fine.

Now, I also want to optimize memory usage. Since I always use one implementation at the time, the internal state of each instance can share the same memory space. This becomes a problem as I can't put inherited instances into a union; compiler is not happy about it (it make sense I guess, because of the vtable probably).

The best solution I found to that problem is to declare a structure of data that uses a union outside of the classes and pass a pointer to it to each instance of the class.

Is there a better way to do?

EDIT : In this case, no dynamic allocation is to be used.

See code below:

The problem

#include <stdio.h>

using namespace std;


class Base{
    public:

    virtual void doprint() = 0;
};

class ChildA : public Base{
    public:
    virtual void doprint(){
        printf("I am A : %d",foo);
    };

    int foo;
};

class ChildB : public Base{
    public:
    virtual void doprint(){
        printf("I am B : %u", bar);
    };

    unsigned int bar;
};

int main()
{
    // Changing this for a struct works
    union{
        ChildA a;
        ChildB b;
    } u;

    // Configure phase
    u.a.foo = -10;
    Base *pbase = &u.a;
    
    // Exec phase
    pbase->doprint();
    
    return 0;
}

The above code make the compiler say: error: union member ‘main()::::a’ with non-trivial ‘ChildA::ChildA()’

The ugly solution

#include <stdio.h>

using namespace std;

union InternalData{
    struct {
        int foo;
    } data_for_a;
    struct {
        unsigned int bar;
    } data_for_b;
};


class Base{
    public:
    void init(InternalData *data)
    {
        m_data = data;
    }
    virtual void doprint() = 0;
    protected:
    InternalData* m_data;
};

class ChildA : public Base{
    public:
    virtual void doprint(){
        printf("I am A : %d", m_data->data_for_a.foo);
    };
    
};

class ChildB : public Base{
    public:
    virtual void doprint(){
        printf("I am B : %u", m_data->data_for_b.bar);
    };
};

int main()
{

    ChildA a;
    ChildB b;
    InternalData internal_data;
    
    // Configure phase
    internal_data.data_for_a.foo = -10;
    a.init(&internal_data);
    Base *pbase = &a;
    
    // Exec phase
    pbase->doprint();
    
    return 0;
}

CodePudding user response:

You don't really want a union (nobody ever really wants a union...) What you really want is a place to place your implementations into, and to get an interface pointer out, and a guarantee that it's properly destroyed afterwards.

std::variant< ChildA, ChildB> impl; //define buffer that correctly destroys
Base *pbase; //declare the Base pointer.

impl = ChildA{}; //assign it an implementation
pbase = std::get<ChildA>(impl); //assign pointer to that implementation

If you C doesn't have std::variant, then you can implement the key pieces for a destructing buffer yourself. A minimal version looks something like this:

template<class Base, std::size_t size, std::size_t align>
class buffer {
    static_assert(std::has_virtual_destructor_v<Base>);
    std::aligned_storage_t<size, align> rawbuffer;
    Base* pbase=0;
public:
    ~buffer() {if (pbase) pbase->~Base();};
    
    Base* get() {return pbase;}
    
    template<class T, class...Us>
    Base* construct(Us&&...vs) {
        static_assert(sizeof(T) <= sizeof(rawbuffer));
        assert(pbase == nullptr);
        pbase = new(reinterpret_cast<T*>(&rawbuffer)) T(std::forward<Us>(vs)...);
        return pbase;
    }
};

Note that you'll almost certainly want your Base to have a virtual destructor. https://coliru.stacked-crooked.com/a/4d08c8b3b9625988

  • Related