Deprecated: mysql_connect(): The mysql extension is deprecated and will be removed in the future: use mysqli or PDO instead in /home/www/novacisko.cz/subdomains/bredy/init.php on line 11

Warning: mysql_connect(): Headers and client library minor version mismatch. Headers:50562 Library:100020 in /home/www/novacisko.cz/subdomains/bredy/init.php on line 11

Warning: Cannot modify header information - headers already sent by (output started at /home/www/novacisko.cz/subdomains/bredy/init.php:11) in /home/www/novacisko.cz/subdomains/bredy/index.php on line 38
Serializace dat a objektů III - Volba formátu - Bredyho blog - Ondřej Novák
Bredyho blog - Ondřej Novák

Serializace dat a objektů III - Volba formátu

V našem seriálu o serializaci si tentokrát ukážeme, jakým způsobem budeme volit formát výsledného streamu. Nepůjde o konkrétní formáty, ale o způsoby, jak různé formáty implementovat


Obecný serializátor

Vraťme se ještě k JednoduchémuSerializátoru a trošku jej zobecníme. Máme tam mnoho deklarací definující operátor závorek () pro spoustu základních typů jako je int, nebo float. Pro ostatní třídy voláme statickou metodu serialize šablony Serializable<>, která je specializovaná pro další známé typy. Specializací této šablony můžeme další typy přidat. Typy, které nemají specializaci jsou zpracovány tak, že se předpokládá, že typem je třída a má definovanou metodu serialize()

V těchto místech skončil minulý díl. Povšimněme si, že mezi základními typy a typy serializované specializací šablony Serializable není až takový rozdíl. I pro ně přece můžeme udělat specializaci, a není třeba je deklarovat ve vlastmí serializátoru.

template<>
class Serializable<bool> {...}
template<>
class Serializable<char> {...}
template<>
class Serializable<int> {...}
template<>
class Serializable<float> {...}
template<>
class Serializable<double> {...}
template<>
class Serializable<std::string> {...}
....
///zkráceno

Předpokládejme, že máme třídu, která tuto serializaci zvládne pro libovolný základni typ. Budeme ji říkat Serializable_BasicType. Její implementací tento článek bude končit, zatím jen předpokládejme, že existuje. Zakládáme jí z důvodu:

  • Snadno oddělit základní typy od ostatních
  • Umět v budoucnu některé typy zařadit do základních
  • Nepsat zbytečně opakující se kód
  • Nemožnost využít obecné šablony Serialize<T> ... protože implementace této šablony již byla představena v předchozím díle.

Třída Serializable_BasicType bude za nás přebírat zodpovědnost za serializaci všech základních typů a jejích použití může vypadat třeba takto:

   template<> class Serializable<bool>: public Serializable_BasicType {};
template<> class Serializable<signed char>: public Serializable_BasicType {};
template<> class Serializable<signed short>: public Serializable_BasicType {};
template<> class Serializable<signed long>: public Serializable_BasicType {};
template<> class Serializable<signed long long>: public Serializable_BasicType {};
template<> class Serializable<float>: public Serializable_BasicType {};
template<> class Serializable<double>: public Serializable_BasicType {};
template<> class Serializable<long double>: public Serializable_BasicType {};
template<> class Serializable<unsigned char>: public Serializable_BasicType {};
template<> class Serializable<unsigned short>: public Serializable_BasicType {};
template<> class Serializable<unsigned long>: public Serializable_BasicType {};
template<> class Serializable<unsigned long long>: public Serializable_BasicType {};

V tomto okamžiku všechny základní typy přebírá na zodpovědnost třída Serializable_BasicType. Je zřejmé, že implementací této třídy bude metoda serialize() (stejně jako v minulém díle), avšak její činností bude volat přímo formátovač/parser, který zajístí vlastní proces serializace (viz dále).

Co nám tedy zbylo v serializátoru?

Serializátor se teď opravdu smrsknul na minimum, takže jej můžeme vypsat celý

class Serializátor {
{
public:
template<typename T>
void operator()(T &x) {
Serializable<T>::serialize(x,*this);
}
};

Je to překvapující? Proč vlastně tvoříme celou třídu? Proč nevoláme metodu serialize přímo? A kde je ten slibovaný kontext z prvního dílu?

Ano poslední otázka naráží kladívkem hlavičku hřebíčku. Přesně to nám tady chybí, třída ještě není úplná, chybí v ní kontext. A po tom co jsme se zbavili v serializátoru všeho zbytečného a delegovali jsme tyto činnosti na externí, lehce programovatelné šablny, zbývá nám tady pouze to jediné, co serializátor vlastně dělá. Udržuje kontext vlastního procesu serializace.

Kontext:

  • Vlastní instance formátovače (vnitřní stav, například stream)
  • Číslo verze proudu (bude se hodit)
  • Uživatelský kontext.
  • Další kontext, například tabulka tříd, nebo index instancí (přiblížíme si v některým pozdějších dílů).

Formátování a parsování proudu

Začněme psát formátovácí a parsovací modul. Bude se pravděpodobně jednat o dvě třídy. Jedna třída bude pro zápis a druhá pro čtení dat.

Díky předchozímu serializátoru a jeho přizpůsobení pomocí třídy Serializable nemusí už modul řešit rozklad na základní typy, a postačí, když bude umět všechny základní, tedy to co převezme třída Serializable_BasicType. Tentokrát musí obě části modulu implementovat všechny typy separátně, ale protože to budou jen základní typy, je jich konečné množství a není potřeba řešit rozšířitelnost. Schopnosti formátovače a parsera budou prostě jednou dané a basta (to co vadilo u serializátoru a díky němuž není potřeba tento problém řešit ve formátovači).

Metoda provádějící vlastní serializaci nechť se nazývá exchange:

class MujFormatter {
public:
void exchange(bool &x);
void exchange(char &x);
void exchange(short &x);
void exchange(int &x);
void exchange(long &x);
};

Přitom i zde si můžeme dovolit zjednodušení. Například nejjednodušší binární formátovač má velice primitivní implementaci

class BinFormatter {
public:
template<class T>
void exchange(T &x) {stream.write((char *)&x,sizeof(x);}
};

class BinParse {
public:
template<class T>
void exchange(T &x) {stream.read((char *)&x,sizeof(x);}
};

Všechny základní typy jsou binárně serializovatelné, takže pro jejich přenesení do proudu nebo z proudu postačí pouze jejich binární reprezentaci skopírovat do/z výsledného proudu. Všimnětet si, že náš primitivní binární formátovač/parser je hotov (za předpokladu, že jej doimplementujeme, tak aby třída například věděla, co je to stream)

Při serializaci do složitější formátů jako XML, nebo jiný formát (třeba 3DS, FBX, INI(cfgparser),) bude formátovač a parser pravděpodobně mnohokrát složitější. Na principu se nic nemění, každý typ, který formátovač umí musí mít svůj formát definovaný jako kód metody exchange pro příslušný typ.

Spojení formátovače a serializátoru

Implementujme nyní formátovač do serializéru.

template<typename Formatter>
class Serializer
{
public:
Serializer(Formatter fmt):fmt(fmt) {}

Formatter &operator->() {return fmt;}

template<typename T> void operator(T& x) {
Serializable<T>::serialize(x,*this);
}
protected:
Formatter fmt;
};

Z třídy Serializer jsme udělali šablonu. Byl to krok logický, doposud nám totiž ve třídě chyběl kontext, takže šablona nebyla nutná. Je ale zřejmé, že pro možnost používat různé formátovače je potřeba mít možnost Serializer přízpůsobit danému formátovači. Šablona se nabízí spíš, než třeba pozdní vazba. Zejména proto, že všechny části serializace již pracují se šablonou, takže snesou cokoliv.

Všimněte si také, jaká se udržuje instance formátovače v třídě. Jako přímá hodnota. Dává to možnost 3 způsobů použití

  1. Předání reference jako Formatter &
  2. Přímá dekompozice jako Formatter (to může fungovat, pokud formatovač dál komunikuje s proudem přes referenci, tím si umažeme jednu referenci v řetězu. Ideální je, pokud samotný proud lze kopírovat například přes sdílení zdrojů. Pak nejen, že nemáme žádné přestupní reference, ale překladač může v konečném důsledku přeložit serializaci ako přímé volání například funkce read/write)
  3. Děděním - Formátovač serializer podědí a vznikne tak jeden velký společný objekt serializer s formátovačem. To umožňuje stav serializace držet pouze v jednom objektu. Konstruktor serializátoru se zavolá s *this.

Do třídy jsme přidali operátor ->. Ten nám zjednoduší volání funkcí formátovače. Protože požadavek na formátovač zpravidla bude přicházet z venku (aby bylo možné právě pomocí šablony Serializable dodatečně upravovat serializaci základních typů), musí být tento operátor veřejný.

Pak můžeme místo

x(var);

napsat

x->exchange(var);

rozdíl je v tom, že první příklad může být rozložen na podtypy, druhý příklad předepisuje, že proměnou musí zpracovat formátovač sám.
Ve vlastním procesu serializace (metoda serialize() s jedním parametrem) nebudeme metodu exchange volat. Už proto, že by serializační předpis ztratil na obecnosti, která je v tomhle případě přímo kritická. Nicméně její existenci můžeme využit při specializaci šablony Serializable pro některé velmi speciální typy, které vyžadují přímou spolupráci s formátovačem.

Třída Serialize_BasicType

Zbývá nám implementace třídy Serialize_BasicType. Jak jsme si řekli výše, cokoliv tato třída serializuje, vlastně přímo volá formátovač/parser. K dispozici přitom má pouze proměnnou, kterou serializuje, a serializátor. Proto jsme si definovali operátor ->, aby bylo možné zkrze serializátor oslovit přímo formátovač a přinutit jej zpracovat předanou proměnnou a typ

class Serializable_BasicType {
public:
template<typename T, typename Arch>
void serialize(T &x, Arch &a) {
a->exchange(x);
}
};

Takto předepisujeme, že všechny typy, jejichž specializace šablony Serializable dědí Serializable_BasicType, budou serializovány přímo pomocí formátovače/parsera.

Vylepšení na závěr

Určitě si vzpomenete na druhý díl, kde bylo řečeno, že každý formátovač může mít jinou množinu základních typů. Jde zejména o různá pole, vektory, množiny i databáze. Možná by bylo vhodné, kdyby existovalo víc variant třídy Serializable, pro každý formátovač/parser jiná.

I to je možné, pokud použijeme následující rozšíření: Zavedeme další parametr šablony serializéru:

template<typename Formatter, template<class> class Serializable>
class Serializer {
};

Na rozdíl od dosavadního přístupu, zde staticky deklarovanou šablonu Serializable zastupuje proměnná šablony, která očekává jeden parametr. To nám umožní definovat konkurenční šablonu, která bude mít vlastní sadu typů a způsobů serializace. Můžeme šablonu spojit s formátovačem a to dokonce tak, že formátovač a parser vystačí pouze s jednou šablonou. Konkurenční šablona může přitom se Serializable spolupracovat a to tak, že typy, které neumí předá třídě Serializable k dalšímu zpracování.

template<typename T>
class MySerializable {
public:
template<typename Archive>
static void serialize(T &x, Archive arch) {
Serializable<T>::serialize(x,arch);
}
};

template<>
class MySerializable<std::string> {
public:
template<typename Archive>
static void serialize(T &x, Archive arch) {
}
};

Serializer<BinFormatter, MySerializable> serializer(...);

Tato ukázka ukazuje, jak přinutit serializaci aby probíhala přes mojí variantu třídy Seriaizable.

Příště

Na začátku se ještě vrátíme k formátovači a jeho propojení s transportním modulem. Ve zbytu se budeme zabývat serializací nazvaných proměnných, s výchozí hodnotou a podíváme se, jak zjišťovat směr serializace a další údaje z kontextu serializátoru.

vytvořeno: 18.9.2008 11:48:28, změněno: 19.9.2008 12:02:52
Jsou informace v článku pro Vás užitečné?
  • (1)
  • (0)
  • (0)
  • (0)
  • (0)
Nick nebo OpenID
Vzkaz
 
25.7.2016 18:27:34

KpvxloRB1

J&rsquo;admire votre compassion, Yves. Il y a peu d&nÃÂuo;arnsƒq©es que j&rsquo;ai assimilé ce mot&#8230;. Je sais que nous devrions tous regarder les autres de cette façon, mais les chasseurs, non, je n&rsquo;y arrive pas.. Pour moi, ils restent des assassins,des tueurs pour le plaisir de tuer, c&rsquo;est tout&#8230;.

Podobné články

Serializace dat a objektů II - Návrh serializátoru

Po teoretickém úvodu o serializaci se podíváme (ještě stále teoreticky) na vlastní serializátor. Zatím si uděláme takovou jednoduchou analýzu

Serializace dat a objektů

Následující článek je úvodem do další série o generickém programování (šablony), nyní se zaměříme na problém perzistentního ukládání dat nebo jejich transport, obecně o serializaci a deserializaci dat

Serializace dat a objektů IV - Transport a struktura

Dnešní díl o serializaci se pozastaví nad nejvhodnější implementací transportního modulu a pak se vrhneme na strukturu dat.

Topolánek útočí na bránu ČSSD, Kraus hraje vysokou holí

Dnešní ráno začalo vskutku zajímavě. Volební hokej se s posledními minutami stává drsnější...

Úvod do obecného OOP 2. díl

V tomto díle prozkoumáme život objektů
Reklama: