Можно ли хранить полиморфный класс в разделяемой памяти?

Предположим, у меня есть класс Base а также Derived : public Base,
Я создал сегмент разделяемой памяти, используя библиотеку boost :: interprocess. Возможно ли иметь код, подобный этому:

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

Проблемы, которые я вижу здесь, к примеру, в том, что требуемое пространство для производного класса Base неизвестно (так сколько же выделить shmem?)

Q: как передавать объекты через указатели между приложениями?

5

Решение

Просто прочитайте его документация

Особенно:

Виртуальность запрещена

Указатель виртуальной таблицы и виртуальная таблица находятся в адресе
пространство процесса, который создает объект, поэтому, если мы помещаем
класс с виртуальной функцией или виртуальный базовый класс, виртуальный
указатель, помещенный в разделяемую память, будет недопустимым для других процессов
и они потерпят крах.

Эту проблему очень трудно решить, так как каждый процесс требует
другой указатель виртуальной таблицы и объект, который содержит это
указатель является общим для многих процессов. Даже если мы отобразим на карте
область в том же адресе в каждом процессе, виртуальная таблица может быть
по другому адресу в каждом процессе. Чтобы включить виртуальные функции
для объектов, совместно используемых процессами, необходимы глубокие изменения компилятора
и виртуальные функции пострадают от снижения производительности. Вот почему
Boost.Interprocess не планирует поддерживать виртуальные функции.
и виртуальное наследование в отображенных областях, совместно используемых процессами.

12

Другие решения

Разделяемая память изначально допускает только структуры POD (в глубине души они могут иметь конструкторы / copy / etc …).

Boost.Interprocess поднимает планку, эмулируя семантику указателей поверх смещений в сегменте разделяемой памяти.

Однако виртуальный указатель не является указателем на чистые данные, это указатель на разделы кода, и здесь все усложняется, потому что разделы кода не обязательно отображаются на один и тот же адрес от одного процесса к другому (даже если они были запущены из одного и того же двоичного файла).

Итак … нет, виртуальные указатели-полиморфные объекты не могут быть сохранены в общей памяти.


Однако тот факт, что многие реализации C ++ решили использовать механизм виртуальных указателей, не означает, что это единственный способ иметь полиморфное поведение. Например, в LLVM и Clang они строят свои закрытые иерархии, чтобы получить полиморфизм без виртуальных указателей (и RTTI), чтобы снизить требования к памяти. Эти объекты могут эффективно храниться в общей памяти.

Итак, как сделать полиморфизм совместимым с разделяемой памятью: нам нужно не хранить указатели на таблицы / функции, однако мы можем хранить индексы.

Пример идеи, но, возможно, можно уточнить.

/// 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

Хорошо … Я думаю, что теперь вы цените магию компилятора …

Что касается использования, к счастью, это намного проще:

/// 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

В действии на ideone.

4

Я считаю, что вы смотрите на сериализацию объектов. Посмотри на http://www.boost.org/doc/libs/1_51_0/libs/serialization/doc/index.html

Несколько способов сделать это:
1. сериализуйте свой класс C ++
2. отправить данные в другое приложение
3. десериализовать в класс C ++.

3

//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;
}
-1
//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() ;
}
-1
По вопросам рекламы [email protected]