Is it possible to store polymorphic class in share

2019-03-16 15:15发布

问题:

Suppose I have class Base and Derived : public Base. I have constructed a shared memory segment using boost::interprocess library. Is it possible to have code similar to this:

Base* b = new Derived(); 
write(b); //one app writes
Base* b2 = read(b); //second app reads
//b equals b2 (bitwise, not the ptr location)

The problems I see here is for instance that the required space for a derived class of Base is unknown (so how much shmem to allocate?)

Q: how to pass objects via pointers between applications?

回答1:

Just read its documentation

In particular:

Virtuality forbidden

The virtual table pointer and the virtual table are in the address space of the process that constructs the object, so if we place a class with a virtual function or virtual base class, the virtual pointer placed in shared memory will be invalid for other processes and they will crash.

This problem is very difficult to solve, since each process needs a different virtual table pointer and the object that contains that pointer is shared across many processes. Even if we map the mapped region in the same address in every process, the virtual table can be in a different address in every process. To enable virtual functions for objects shared between processes, deep compiler changes are needed and virtual functions would suffer a performance hit. That's why Boost.Interprocess does not have any plan to support virtual function and virtual inheritance in mapped regions shared between processes.



回答2:

Shared memory originally only allows POD structures (at heart, they may have constructors/copy/etc...).

Boost.Interprocess raises the bar by emulating pointers semantics on top of offsets into the shared memory segment.

However, a virtual pointer is not a pointer to pure data, it's a pointer to code sections, and that is where things get complicated because code sections are not necessarily mapped to the same address from one process to another (even if they were launched from the same binary).

So... no, virtual pointers-polymorphic objects cannot be stored in shared memory.


However, just because many C++ implementations chose to use a virtual-pointer mechanism does not mean that this is the only way to have polymorphic behavior. For example, in LLVM and Clang they build on their closed hierarchies to get polymorphism without virtual pointers (and RTTI) so as to lower memory requirements. Those objects could, effectively, be stored in shared memory.

So, how to get polymorphism compatible with shared memory: we need not to store pointers to tables/functions, however we can store indexes.

Example of the idea, but could probably be refined.

/// In header
#include <cassert>

#include <vector>

template <class, size_t> class BaseT;

class Base {
    template <class, size_t> friend class BaseT;
public:

    int get() const; //     -> Implement: 'int getImpl() const' in Derived

    void set(int i); // = 0 -> Implement: 'void setImpl(int i)' in Derived

private:
    struct VTable {
        typedef int (*Getter)(void const*);
        typedef void (*Setter)(void*, int);

        VTable(): _get(0), _set(0) {}

        Getter _get;
        Setter _set;
    };

    static std::vector<VTable>& VT(); // defined in .cpp

    explicit Base(size_t v): _v(v) {}

    size_t _v;
}; // class Base

template <class Derived, size_t Index>
class BaseT: public Base {
public:
    BaseT(): Base(Index) {
        static bool const _ = Register();
        (void)_;
    }

    // Provide default implementation of getImpl
    int getImpl() const { return 0; }

    // No default implementation setImpl

private:
    static int Get(void const* b) {
        Derived const* d = static_cast<Derived const*>(b);
        return d->getImpl();
    }

    static void Set(void* b, int i) {
        Derived* d = static_cast<Derived*>(b);
        d->setImpl(i);
    }

    static bool Register() {
        typedef Base::VTable VTable;

        std::vector<VTable>& vt = Base::VT();

        if (vt.size() <= Index) {
            vt.insert(vt.end(), Index - vt.size() + 1, VTable());
        } else {
            assert(vt[Index]._get == 0 && "Already registered VTable!");
        }

        vt[Index]._get = &Get;
        vt[Index]._set = &Set;
    }
}; // class BaseT

/// In source
std::vector<VTable>& Base::VT() {
    static std::vector<VTable> V;
    return V;
} // Base::VT

int Base::get() const {
    return VT()[_v]._get(this);
} // Base::get

void Base::set(int i) {
    return VT()[_v]._set(this, i);
} // Base::set

Okay... I guess that now you appreciate the compiler's magic...

Regarding the usage, it's fortunately much simpler:

/// Another header
#include <Base.h>

// 4 must be unique within the hierarchy
class Derived: public BaseT<Derived, 4> {
     template <class, size_t> friend class BaseT;
public:
    Derived(): _i(0) {}

private:
    int getImpl() const { return _i; }

    void setImpl(int i) { _i = i; }

    int _i;
}; // class Derived

In action at ideone.



回答3:

I believe you are looking at serialization of objects. Have a look at http://www.boost.org/doc/libs/1_51_0/libs/serialization/doc/index.html

A few ways you can do is: 1. serialize your C++ class 2. send data to another app 3. deserialize into C++ class.



回答4:

//From the example above , I have removed VTable 
// I also removed static variables as per boost::interprocess 
// static variable don't work with shared memory, and also I did not see
// any advantage in actually builting a VTable for all derived classes
#include <vector>
#include <boost/bind.hpp>
#include <boost/function.hpp>

template <class> class BaseT;

class Base {
    template <class> friend class BaseT;
    boost::function< int (void) > _get;
    boost::function< void (int) > _set;
public:

    int get() {
        return _get();
    } //     -> Implement: 'int get() ' in Derived

    void set(int i) {
        _set(i);
    } // = 0 -> Implement: 'void set(int i)' in Derived
}; // class Base

template <class Derived>
class BaseT : public Base {

public:
    BaseT() : Base(), impl(static_cast<Derived *> (this)) {
        Base::_get = boost::bind(&BaseT<Derived>::get, this);
        Base::_set = boost::bind(&BaseT<Derived>::set, this, _1);
    }

    int get() {
        return impl->get();
    }

    void set(int i) {
        impl->set(i);
    }

private:
    Derived * impl;
};


//some A  implementation of Base
struct A : BaseT<A> {

    int get() {
        return 101; //testing implementation
    }

    void set(int i) {
        ; //implementation goes here
    }
};

//some B  implementation of Base
struct B : BaseT<B> {

    int get() {
        return 102; //testing implementation 
    }

    void set(int i) {
        ; //implementation goes here
    }
};

int main() {
    BaseT<A> objectA;
    BaseT<B> objectB;
    Base *a = &objectA;
    Base *b = &objectB;
    std::cout << a->get() << " returned from A class , "
            << b->get() << " returned from B class " << std::endl;
    return 0;
}


回答5:

//While redefining I changed semantics of constnance in getter, 
//and had non-    const Derived pointer used for both getter and setter. 
//But original simantics can be preserved as following:

    int get() const {
         //return impl->get();
        //this enforces that get has to be const
        static_cast<const Derived *> (this)->get() ;
    }