Na kurzech se velmi často setkávám s dotazem,
jaký je rozdíl mezi šablonovou verzí seznamu (CList) a třídní verzí (CObList).
Vzhledem k tomu, že neexistuje jednoznačná odpověď typu: zásadně používejte CList
necháme proběhnout boj mezi oběma třídami a to v několika kolech.
Abychom mohli oba přístupy srovnávat, vytvoříme si následující
třídu (pro jednoduchost uvádím pouze deklarace):
class PRO_Ancest : public CObject
{
// Data
// -----------------------------------------------
public:
int m_nData;
// Konstrukce
// -----------------------------------------------
PRO_Ancest();
// Virtuální operace
// -----------------------------------------------
virtual void Print();
virtual void Serialize(CArchive &ar);
} |
Soupeře máme, máme i s čím bojovat (že by C++?), tedy vzhůru do
bitvy.
Kolo první - univerzálnost
Šablona CList nám umožňuje vytvářet obousměrně
vázané seznamy libovolného typu, přičemž tento typ je určen až v definici
proměnné (většinou členské proměnné). Naopak třída CObList umožňuje
vytvářet pouze seznamy ukazatelů na objekty typu CObject. Vezmeme-li
tedy hledisko univerzálnosti, vítězem je šablonová verze seznamu a dostáváme se na
skóre 1:0 pro šablony..
// Definice seznamu ukazatelů na CObject a potomky
CObList oListCla;
// Definice seznamu ukazatelů na PRO_Ancest a potomky
CList<PRO_Ancest*, PRO_Ancest*> oListTem;
// Vytvoření nějakých instancí
PRO_Ancest *poAnc = new PRO_Ancest();
// Vložení do prvního seznamu
oListCla.AddTail(poAnc);
// Vložení do druhého seznamu
oListTem.AddTail(poAnc); |
Jak je vidět, tak mezi šablonovou a třídní verzí není patrný
žádný rozdíl a skóre tedy zůstává stále na 1:0.. Ale to je jen zdání, které
hned vyvrátíme v následujícím fragmentu kódu:
Kolo druhé - bezpečnost
// Vytvoříme instanci třídy, která dědí z CObject
CWnd *poWnd = new CWnd();
oListCla.AddTail(poWnd); // OK
oListTem.AddTail(poWnd); // Synt. chyba |
A je to tady - vložení ukazatele na instanci třídy CWnd do
seznamu je asi chyba, to je zřejmé, ale třídní verze to za chybu nepovažuje, kdežto
pro šablonovou verzi to chyba je, a to dokonce syntaktická. Díky tomu, že šablonová
verze je konkrétnější, podléhá kód lepší syntaktické kontrole. Z toho plyne, že
šablony získávají bod a skóre je 2:0.
Podívejme se na přístup k prvkům v seznamu (například k tomu poslednímu):
PRO_Ancest *poAncest;
poAncest = (PRO_Ancest*) oListCla.GetTail(); // přetyp. je nutné
poAncest = oListTem.GetTail(); |
A vzhledem k tomu, že přetypování není vůbec bezpečná technika
(lze přetypovat cokoli na cokoliv), získává šablona další bod a posouvá skóre na 3:0
ve svůj prospěch.
Kolo třetí - perzistence
Poslední disciplínou, ve které necháme seznamy soutěžit, je
perzistentnost (ukládání na disk) - mechanismus serializace. Jak šablona CList,
tak i třída CObList mají virtuální metody Serialize pracující s archivem.
Ukládání by tedy vypadalo takto:
// instance ar je typu CArchive& a získali jsme ji například
// v metodě Serialize dokumentu
oListCla.Serialize(ar);
oListTem.Serialize(ar); |
Zdá se, že skóre zůstane stejné, ale není tomu tak, protože bod
překvapivě získává třídní verze seznamu - stav tedy je 3:1. Že
by nespravedlivý rozhodčí? Vůbec ne! Je to za to, že šablonová verze uloží
naprosté nesmysly. Musíme si vysvětlit jak fungují metody Serialize v
jednotlivých případech. Třídní verze Serialize dělá to, že kromě
interních dat zavolá pro všechny vložené instance jejich metody Serialize, čímž
jím dá možnost uložit se. Šablonová verze kromě uložení interních dat udělá
to, že na disk uloží ukazatele, které po vypnutí a zapnutí samozřejmě ztrácejí
význam a pokud k nim přistoupíme, dojde pravděpodobně k pádu aplikace.
Závěr
Výsledek 3:1 asi mluví za vše. Všude kde to jen
lze, používejme šablonové verze (platí i pro CArray a CMap), které
jsou bezpečnější a univerzálnější. Jediné na co musíme dávat pozor, je
použití mechanismu Serialize, který u šablonových verzí musíme
implementovat vlastními silami, což ale není nic složitého (tři řádky kódu). |