Współdzielona pamięć początkowo pozwala tylko na konstrukcje POD (w zasadzie mogą mieć konstruktory/kopie/etc ...).
Boost.Interprocess
podnosi poprzeczkę, emulując semantykę wskaźników na szczycie przesunięć w segmencie pamięci wspólnej.
Jednak wskaźnik wirtualny nie jest wskaźnikiem do czystych danych, jest wskaźnikiem do sekcji kodu, i to jest, gdzie rzeczy się komplikują, ponieważ sekcje kodu niekoniecznie są mapowane na ten sam adres z jednego procesu do innego (nawet jeśli zostały uruchomione z tego samego pliku binarnego).
Tak ... nie, wskaźniki wirtualne - obiekty polimorficzne nie mogą być przechowywane we wspólnej pamięci.
Jednak tylko dlatego, że wiele implementacji C++ zdecydowali się wykorzystać mechanizm virtual-pointer nie oznacza, że jest to jedyny sposób, aby mieć polimorficzne. Na przykład w LLVM i Clang budują na swoich zamkniętych hierarchiach, aby uzyskać polimorfizm bez wirtualnych wskaźników (i RTTI), aby zmniejszyć wymagania dotyczące pamięci. Te obiekty mogłyby być skutecznie przechowywane we wspólnej pamięci.
Tak, jak się polimorfizm zgodny z pamięci współdzielonej: musimy nie przechowywać wskaźniki do tablic/funkcji, jednak możemy przechowywać indeksy.
Przykład pomysłu, ale mógłby prawdopodobnie zostać dopracowany.
/// 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
porządku ... myślę, że teraz docenić kompilator magia ...
odniesieniu do użytkowania, to na szczęście o wiele prostsze:
/// 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
w akcji na ideone.
super, dokładnie to, czego oczekiwaliśmy. Dzięki! – Queequeg
@Queequeg: Co ciekawe, widziałem wykorzystanie nazwanych segmentów pamięci współdzielonej z obiektami polimorficznymi. W tym konkretnym przypadku pojedynczy proces uzyskuje dostęp do segmentu (jednocześnie), a segment pamięci współdzielonej jest wykorzystywany, więc w przypadku awarii procesu, po ponownym uruchomieniu może on znaleźć cały swój stan z powrotem. Wymaga to przepisywania wszystkich wirtualnych wskaźników, więc jest zdecydowanie zaangażowany. –