Модераторы: Daevaorn

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Сериализация, наверное, не знаю как тему назвать правильно 
:(
    Опции темы
georain
  Дата 15.2.2008, 15:18 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бывалый
*


Профиль
Группа: Участник
Сообщений: 193
Регистрация: 28.11.2006
Где: Санкт-Петербург

Репутация: нет
Всего: нет



Вот приходят из сети данные блоками, в начале блока есть число - идентификационный номер класса который он содержит. Нужно преобразовать блок в класс с помощью статической функции этого класса.
Пользоваться case не хочу, классов много, хочу чтобы работало быстро smile

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

Код


//CDR - потоковый класс
CDR cdr(input);

//каждый класс содержит функцию create()
class ClassA: public Base{
    public:

    virtual ~ClassA(){};

    static Base * create(CDR * cdr){
        ClassA * bp = new ClassA;
        // тут инициализируем данными, например
        // member = cdr->getMember();
        return bp;
    };    
};



Делаю enum - перечисляю все мои классы, номера для каждого класса беру из протокола
Код

enum{
nClassA = 1,
nClassB,
nClassC
};


Далее делаю финт ушами

Код

//создаю класс - массив функций
class VTable{
    public:
    Base * (*VFunc[100])(CDR *);

    //конструктор
    VTable(){
    //делаю define для упрощения записи
    #define f(CN)   VFunc[n##CN] = CN::create;

    //записываю как в enum
    f(ClassA)
    f(ClassB)
    f(ClassC)

    };
} vt;


Для создания нужного класса пишу
Код

Base * bptr = (*vt.VFunc[cdr.getNumber()])(&cdr);

В итоге вместо множественного case получаю быстрый вызов нужной функции.

Реализация мне нравится, но может есть что получше? Потому что во-первых велосипед наверняка, а во вторых надо не забыть каждый класс в таблицу записать и в enum ещё.

Это сообщение отредактировал(а) georain - 15.2.2008, 15:23
PM MAIL   Вверх
marcusmae
Дата 15.2.2008, 15:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


stravaganza
**


Профиль
Группа: Участник
Сообщений: 874
Регистрация: 26.3.2006

Репутация: 5
Всего: 39



georain, действительно, велосипед солидный. Задача, как я понял, из потока распознать и десериализовать объекты. Если у вас дерево классов полиморфное, то почему нельзя эту иерархию как-то удобнее закодировать, чем прямой нумерацией? От этого в итоге будет зависеть, на сколько быстро их будет распознать.


--------------------
ἀπὸ μηχανῆς θεός
PM MAIL ICQ GTalk   Вверх
Alek86
Дата 15.2.2008, 15:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1299
Регистрация: 30.1.2007
Где: Киев

Репутация: 21
Всего: 25



удалил

Это сообщение отредактировал(а) Alek86 - 15.2.2008, 15:56


--------------------
user posted image    user posted image
PM MAIL   Вверх
georain
Дата 15.2.2008, 16:36 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бывалый
*


Профиль
Группа: Участник
Сообщений: 193
Регистрация: 28.11.2006
Где: Санкт-Петербург

Репутация: нет
Всего: нет



Цитата(marcusmae @ 15.2.2008,  15:53)
georain, действительно, велосипед солидный. Задача, как я понял, из потока распознать и десериализовать объекты. Если у вас дерево классов полиморфное, то почему нельзя эту иерархию как-то удобнее закодировать, чем прямой нумерацией? От этого в итоге будет зависеть, на сколько быстро их будет распознать.

Ну так я и прошу совета, подскажите какой есть способ лучше.

На счёт нумерации классов, а как можно удобнее закодировать, чем это будет лучше и чем плохо прямой нумерацией?
PM MAIL   Вверх
Alek86
Дата 15.2.2008, 16:46 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1299
Регистрация: 30.1.2007
Где: Киев

Репутация: 21
Всего: 25



Цитата(georain @  15.2.2008,  15:18 Найти цитируемый пост)
надо не забыть каждый класс в таблицу записать и в enum ещё.

думаю, тут можно так извратиться
Код


enum {nClassA = 1, nClassB, nClassC, nClassE};

class ClassA {};
class ClassB {};
class ClassC {};
class ClassD {}; // нету в енаме, но есть в CREATE_NUMBER_FROM_CLASS
class ClassE {}; // есть в енаме, но нет в CREATE_NUMBER_FROM_CLASS

template<class T> struct Type2Int {/*тут enum'a нету*/};

#define CREATE_NUMBER_FROM_CLASS(class_) \
template<> struct Type2Int<class_> {enum {value = n##class_};};


CREATE_NUMBER_FROM_CLASS(ClassA)
CREATE_NUMBER_FROM_CLASS(ClassB)
CREATE_NUMBER_FROM_CLASS(ClassC)
//CREATE_NUMBER_FROM_CLASS(ClassD)




int main () {
  std::cout << Type2Int<ClassA>::value << std::endl;
  std::cout << Type2Int<ClassB>::value << std::endl;
  std::cout << Type2Int<ClassC>::value << std::endl;
//  std::cout << Type2Int<ClassD>::value << std::endl;
//  std::cout << Type2Int<ClassE>::value << std::endl;
};




раскоментируй все строки и, вникая, увидишь, что при отсутсвии или номера или связи между номером и типом будет бабах (причем, в общем, читабельный) smile

Добавлено @ 16:49
а способа лучше я не смог придумать (применимо к передаче кода класса вначале).

Это сообщение отредактировал(а) Alek86 - 15.2.2008, 16:50


--------------------
user posted image    user posted image
PM MAIL   Вверх
Lazin
Дата 15.2.2008, 17:03 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 3820
Регистрация: 11.12.2006
Где: paranoid oil empi re

Репутация: 41
Всего: 154



Думаю наиболее стандартным решением будет использование указателей на производящие ф-ии

Код

typedef Base* (create_fn*) (CDR * cdr);

std::map<int, create_fn> factory;

потом когда приходит пакет можно очень быстро найти нужную ф-ю, скорость конечно ниже чем у switch, зато эта реализация масштабируется
PM MAIL Skype GTalk   Вверх
Mayk
Дата 15.2.2008, 17:16 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


^аВаТаР^ сообщение>>
****


Профиль
Группа: Участник
Сообщений: 2616
Регистрация: 22.5.2005
Где: за границей разум а

Репутация: 45
Всего: 134



Цитата(Lazin @  15.2.2008,  21:03 Найти цитируемый пост)

потом когда приходит пакет можно очень быстро найти нужную ф-ю, скорость конечно ниже чем у switch, зато эта реализация масштабируется

В принципе вместо map'а можно использовать hash_map или даже vector.
Ну а фабрику можно инициализировать статически
Цитата

class A{
staticA* create(CDR* ){
}
};

class RegisterA
{
public:
RegisterA(){
factory[#number-for-A#] = A::create;
};  
};
static RegisterA regA; 





--------------------
 Здесь был кролик. Но его убили.
Человеки < кроликов, йа считаю.
PM MAIL WWW ICQ   Вверх
marcusmae
Дата 15.2.2008, 17:20 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


stravaganza
**


Профиль
Группа: Участник
Сообщений: 874
Регистрация: 26.3.2006

Репутация: 5
Всего: 39



georain, я бы посмотрел, например, следующее : пусть есть всем базовый класс А, его наследники A1, A2, у каждого их которых по паре своих - A11, A12 и A21, A22 соотвественно. Кодировать - цепочку наследования, скажем, для A22 : просто "A-A2-A22" или, нумируя наследников, "00-01-01" - битами, байтами, символами - как-угодно. Ещё пример для A12 : "00-00-01" и т.п. Что за цепочки получились? = Это - пути в дереве наследования. Таким образом, проблема поиска типа сведена к задаче поиска в дереве. Оно, конечно, имеет свои ньюансы, скажем, в отличие от case по прямой нумерации, по ходу поиска отсекается большое число вариантов.

Цитата(georain @  15.2.2008,  16:36 Найти цитируемый пост)
Ну так я и прошу совета, подскажите какой есть способ лучше.

georain, можно много-чего посоветовать, способов много, тк проблема весьма абстрактная.  А принятие ответственности за выбор способа решения для Вашего конкретного бизнеса - одна из составляющих работы smile


--------------------
ἀπὸ μηχανῆς θεός
PM MAIL ICQ GTalk   Вверх
Lazin
Дата 15.2.2008, 18:26 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 3820
Регистрация: 11.12.2006
Где: paranoid oil empi re

Репутация: 41
Всего: 154



а так более кошерно)) 
Код

class A{
staticA* create(CDR* ){
}
};
namespace {
 class RegisterA
 {
  public:
  RegisterA(){
   factory[#number-for-A#] = A::create;
  };  
 };
 RegisterA regA; 
};//namespace


Это сообщение отредактировал(а) Lazin - 16.2.2008, 13:28
PM MAIL Skype GTalk   Вверх
Alek86
Дата 16.2.2008, 13:13 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1299
Регистрация: 30.1.2007
Где: Киев

Репутация: 21
Всего: 25



marcusmae, раз говоришь, что есть много способов создания классов по данным из потока, то не мог бы ты сказать, как это сделать лучше для N классов, не связанных родственными узами? ну, или ссылку, как обычно smile

потому как мне задачка понравилась, но ничего лучше представленного в пером посте (ну, + некоторые мета-уловки, которые, возможно, упростят жизнь при добавлении новых классов) не придумал


--------------------
user posted image    user posted image
PM MAIL   Вверх
marcusmae
Дата 16.2.2008, 14:13 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


stravaganza
**


Профиль
Группа: Участник
Сообщений: 874
Регистрация: 26.3.2006

Репутация: 5
Всего: 39



Цитата(Alek86 @  16.2.2008,  13:13 Найти цитируемый пост)
N классов, не связанных родственными узами

хорошо сказал  smile
вообще-то с самого начала речь шла о полиморфном дереве классов, ну а если нет, то его всегда можно искусственно создать, определив, к примеру, базовый абстрактный класс :

Код

template<class T> class ISerializable {
    virtual char* get_ID() { return "root"; }

    virtual char* Serialize() = 0;
    virtual T Deserialize(char* stream) = 0;
};


Уже после того, как вчера появились последние посты, я подумал, а как вообще нужно докатиться (в негативном смысле) до ситуации, когда в потоке непонятно-какие объекты, типы которых подлежат определению? Вижу две возможности :

1) на машине в сети работает программа с функцией main(), в которой наинициализирована и сериализована целая армия экземпляров разных типов. Но вообще-то так в кучу обычно не сваливают, а создают большой класс-контейнер Main или, как в .NET, Program, для которого уже можно определить функцию сериализации (которая в т.ч. будет вызывать сериализацию составных компонентов) и отправлять в поток уже как целостный объект.

2) экземпляр класса не может сериализовать сам себя. Если он сериализовался, то что-то извне этого потребовало : явный вызов или исключение. Это можно было бы обыграть, чтобы как-то дополнительно облегчить работу серверу.



--------------------
ἀπὸ μηχανῆς θεός
PM MAIL ICQ GTalk   Вверх
Alek86
Дата 16.2.2008, 15:08 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1299
Регистрация: 30.1.2007
Где: Киев

Репутация: 21
Всего: 25



ну "вот так вот получилось"

причем я легко могу представить, чтобы мне пришел некий отсериализированный контейнер разнородных данных. да пусть и однородных. Как их в этом случае рассовывать по классам? я вижу только вариант с ID


--------------------
user posted image    user posted image
PM MAIL   Вверх
marcusmae
Дата 16.2.2008, 17:10 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


stravaganza
**


Профиль
Группа: Участник
Сообщений: 874
Регистрация: 26.3.2006

Репутация: 5
Всего: 39



Цитата(Alek86 @  16.2.2008,  15:08 Найти цитируемый пост)
"вот так вот получилось"


Alek86, получиться может много-чего, просто улучшаемость заведомо плохих решений весьма ограничена.

Ты сказал ключевое слово контейнер. Раз он есть, значит 1) его тип известен - можно сразу вызывать его десериализатор 2) "рассовывать" - забота контейнера, он должен уметь своим методом Deserialize прочитать, что в нём лежит. Этот вариант я описал в (1). А не нравится мне вариант, когда контейнера нет. То есть, просто ткнули заголовок и указатель на байты и длину - то, о чём шла речь вначале. Вот мне интересно : зачем тот, кто это сделал, так сделал?


Это сообщение отредактировал(а) marcusmae - 16.2.2008, 17:18


--------------------
ἀπὸ μηχανῆς θεός
PM MAIL ICQ GTalk   Вверх
tol05
Дата 16.2.2008, 19:33 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Участник Клуба
Сообщений: 1632
Регистрация: 21.12.2006
Где: Харьков

Репутация: 1
Всего: 170



Привет всем. 
Я давно забыл плюсы и поэтому не буду писать здесь код.
Я не очень четко понял задачу в части
Цитата(georain @  15.2.2008,  14:18 Найти цитируемый пост)
В каждом классе делаю статическую функцию которая принимает указатель на данные, возвращает указатель на базовый класс указывающий на новый класс (производный с нужным номером) созданный из этих данных
Непонятно почему выбрана такая реализация: и в базовом, и в дочернем класса будут статические ф-ции, и какая связь между статическими ф-циями базового и дочернего классов? 

Но меня попросили посмотреть тему и высказаться, поэтому напишу свое видение вопроса концептуально  smile 

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

Соответственно, для того, чтобы определить, в каком порядке этот поток содержит даннные нашего класса, необходимо построить и зафиксировать граф серилизации. Граф создается при серилизации объекта, при десерилизации данные считываются в соответствии с ним. 
Для сложного (композитного) объекта подразумевается, что не только он, но и все его поля поддерживают серилизацию, т.е. каждое поле само должно восстановить себя из потока (ну и записать себя в поток при серилизации, конечно). При этом ... 

Например класс Base содержит в себе поля int, Class1, string. Класс Derived (наследник от Base добавляет bool, Class2, float). Все классы (Base, Derived, Class1 и Class2) наследуют базовый класс 
Код

class ISerializable 
{
    virtual void Serialize(char* stream) = 0;
    virtual ISerializable Deserialize(char* stream) = 0;
};

При серилизации вызывается метод Serialize класса Derived, с такой псевдо-реализацией:
Код

public override void Serialize(char* stream)
{
base.Serialize(stream);

//запись в stream значения поля bool

//вызов (для наглядности так напишу)      
ISerializable isClass2 = this.Class2;
isClass2.Serialize(stream);

//запись в stream значения поля float
}

где
метод Serialize класса Base, с такой псевдо-реализацией:
Код

public override void Serialize(char* stream)
{
//запись в stream значения поля int

//опять же, для наглядности
ISerializable isClass1 = this.Class1;
isClass1.Serialize(stream);

//запись в stream значения поля string
}

при десерилизации вызывается конструктор класса Derived, потом метод Deserialize с такой псевдо-реализацией:
Код

public override ISerializable Deserialize(char* stream)
{
base.Deserialize(stream);//десерилизация унаследованных членов

//чтение из stream значения поля bool

this.Class2 =  new Class2();
this.Class2.Deserialize(stream);

//чтение из stream значения поля float

return this;
}

метод Deserialize класса Base:
Код

public override ISerializable Deserialize(char* stream)
{
//чтение из stream значения поля int

this.Class1 =  new Class1();
this.Class1.Deserialize(stream);

//чтение из stream значения поля string

return this;
}

как видно, за позицию курсора потока беспокоиться не нужно - если все классы серилизуют и десерилизуют себя в одном и том же порядке (граф серилизации, определенный в Derived.Serialize используется в Derived.Deserialize), то каждый поток будет его смещать на величину именно своих данных и все будет хоккей.. Закон простой - в каком порядке записывались данные, в таком и читать. Классы сами себя "рассуют" куда нужно (потому что разработчик классов сам определяет что, как и когда писать и читать. Один раз и навсегда, для каждого класса)

Здесь явно виден недостаток: постоянные вызовы конструкторов и ф-ций Deserialize... А вот если через конструктор базового класса (с параметром потоком) делать... то его бы не было smile
и никаких индексов... они просто не имеют смысда в последовательном и единственном потоке.
Ну вот и все. Не знаю, понятна ли мысль... Но это ясная и четкая структура, полиморфная и весьма юзабельная.
Спасибо за внимание smile

Это сообщение отредактировал(а) tol05 - 16.2.2008, 19:36


--------------------
На хорошей работе и сны хорошие снятся.
PM MAIL   Вверх
marcusmae
Дата 16.2.2008, 20:58 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


stravaganza
**


Профиль
Группа: Участник
Сообщений: 874
Регистрация: 26.3.2006

Репутация: 5
Всего: 39



tol05, спасибо за ответ!

Цитата(tol05 @  16.2.2008,  19:33 Найти цитируемый пост)
Здесь явно виден недостаток: постоянные вызовы конструкторов и ф-ций Deserialize... А вот если через конструктор базового класса (с параметром потоком) делать... то его бы не было 


Согласен, что присутствие спецконструктора полезно в любом случае. А фишка наличия виртуальной функции в том, что с помощью шаблона она возвращает точный тип, а не интерфейс :

(плюсифицирую код tol05  smile)
Код

public virtual ISerializable Deserialize(char* stream)
{
Base::Deserialize(stream);//десерилизация унаследованных членов

//чтение из stream значения поля bool

this->Class2 =  new Class2();
this->Class2->Deserialize(stream);

//чтение из stream значения поля float

return this;

}


Здесь внутри был нужный нам конкретный Class1, но при возврате он разобщился до ISerializable, который потом придётся обратно приводить к Class1, правильно?


--------------------
ἀπὸ μηχανῆς θεός
PM MAIL ICQ GTalk   Вверх
Ответ в темуСоздание новой темы Создание опроса
Правила форума "С++:Общие вопросы"
Earnest Daevaorn

Добро пожаловать!

  • Черновик стандарта C++ (за октябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика(4.4мб).
  • Черновик стандарта C (за сентябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика (3.4мб).
  • Прежде чем задать вопрос, прочтите это и/или это!
  • Здесь хранится весь мировой запас ссылок на документы, связанные с C++ :)
  • Не брезгуйте пользоваться тегами [code=cpp][/code].
  • Пожалуйста, не просите написать за вас программы в этом разделе - для этого существует "Центр Помощи".
  • C++ FAQ

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.0969 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.