Предположим, у меня есть класс 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: как передавать объекты через указатели между приложениями?
Просто прочитайте его документация
Особенно:
Виртуальность запрещена
Указатель виртуальной таблицы и виртуальная таблица находятся в адресе
пространство процесса, который создает объект, поэтому, если мы помещаем
класс с виртуальной функцией или виртуальный базовый класс, виртуальный
указатель, помещенный в разделяемую память, будет недопустимым для других процессов
и они потерпят крах.Эту проблему очень трудно решить, так как каждый процесс требует
другой указатель виртуальной таблицы и объект, который содержит это
указатель является общим для многих процессов. Даже если мы отобразим на карте
область в том же адресе в каждом процессе, виртуальная таблица может быть
по другому адресу в каждом процессе. Чтобы включить виртуальные функции
для объектов, совместно используемых процессами, необходимы глубокие изменения компилятора
и виртуальные функции пострадают от снижения производительности. Вот почему
Boost.Interprocess не планирует поддерживать виртуальные функции.
и виртуальное наследование в отображенных областях, совместно используемых процессами.
Разделяемая память изначально допускает только структуры 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.
Я считаю, что вы смотрите на сериализацию объектов. Посмотри на http://www.boost.org/doc/libs/1_51_0/libs/serialization/doc/index.html
Несколько способов сделать это:
1. сериализуйте свой класс C ++
2. отправить данные в другое приложение
3. десериализовать в класс C ++.
//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;
}
//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() ;
}