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

Предположим, у меня есть класс 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 выделить?)

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


person Queequeg    schedule 19.10.2012    source источник


Ответы (5)


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

Особенно:

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

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

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

person CashCow    schedule 19.10.2012
comment
отлично, именно то, что я ожидал. Спасибо! - person Queequeg; 19.10.2012
comment
@Queequeg: Интересно, что я видел использование именованных сегментов общей памяти с полиморфными объектами. В этом конкретном случае один процесс когда-либо обращается к сегменту (одновременно), и используется сегмент общей памяти, поэтому в случае сбоя процесса после перезапуска он может восстановить все свое состояние. Однако он включает в себя переписывание всех виртуальных указателей, поэтому он определенно задействован. - person Matthieu M.; 19.10.2012

Общая память изначально допускает только структуры POD (по сути, они могут иметь конструкторы/копии/и т. д.).

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.

person Matthieu M.    schedule 19.10.2012

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

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

person xijing dai    schedule 19.10.2012
comment
+1! Как сказал Тони Хоар в CSP: Не общайтесь, делясь, делитесь, общаясь. - person Matthieu M.; 19.10.2012
comment
конечно, вы можете отправить производный класс. Но не как полиморфизм. - person CashCow; 19.10.2012

person    schedule
comment
Я вижу, что кто-то с +10 сказал, что невозможно хранить полиморфные классы, и кто-то проголосовал за меня. Я предлагаю перед голосованием поместить в Google это время компиляции и полиморфизм времени выполнения в С++. Несмотря на то, что CashCow получил 10 голосов, его утверждение относится только к виртуальным таблицам — атрибутам полиморфных классов времени выполнения. Сообщество значительно опередило эти старомодные решения, мы уже используем С++ 17. Пожалуйста, прочитайте исходный вопрос: возможно ли хранить полиморфный класс в общей памяти? . В нем конкретно не говорится, является ли это полиморфизмом времени выполнения или полиморфизмом времени компиляции. - person Vladimir Venediktov; 05.06.2017

person    schedule
comment
Я вижу, что кто-то с +10 сказал, что невозможно хранить полиморфные классы, и кто-то проголосовал за меня. Я предлагаю перед голосованием поместить в Google это время компиляции и полиморфизм времени выполнения в С++. Несмотря на то, что CashCow получил 10 голосов, его утверждение относится только к виртуальным таблицам — атрибутам полиморфных классов времени выполнения. Сообщество значительно опередило эти старомодные решения, мы уже используем С++ 17. - person Vladimir Venediktov; 05.06.2017