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
Automatické klonování objektů v C++ - Bredyho blog - Ondřej Novák
Bredyho blog - Ondřej Novák

Automatické klonování objektů v C++

C++ nenabízí standardní prostředky jako klonovat objekty. Pouze kopírovací kontruktor, který nám však v případě polymorfních objektů moc nepomůže. Naše "lenost" nám však pomůže nalézt řešení.


Jak klonovat

Pokud máme nějaký interface, stačí dopsat virtuální metodu, která provede kopii objektu

class Interface {
public:
...
virtual Interface *clone() const = 0;
};

Tohle je celkem jednoduché. Máme-li k dispozici jakýkoliv objekt, o kterém víme, že implementuje náš Interface, zavoláme metodu clone() a získáme dynamicky alokovanou plnou kopii objektu, aniž by jsme o původním objektu něco věděli

Jak klonovat ještě snadněji

Aby každé rozhraní nemuselo řešit klonování, můžeme naimplementovat společné rozhraní pro klonování.

class IClonable {
public:
virtual IClonable *clone() const = 0;
};

Má přirozená "lenost" mi však říká, že to není úplně košer. Používání téhle třídy mi sice umožňujě "standardně" klonovat libovolné objekty, které implementují IClonable, avšak musím provádět přetypování výsledků. To se mi vůbec nelíbí. Ideální by bylo, abych to nemusel dělat v případě, že klonuju objekty na 'Base' úrovni, tedy na úrovni rozhraní, který dědí IClonable

IClonable
Base
Derived1
Derived2
...

Upravíme tedy tenhle kód tak, aby Base::clone() generovalo ukazatel na Base, zatimco ICLonable::clone na IClonable

template<class Base>
class IClonable;

template<>
class IClonable<void> {
public:

IClonable *clone() const {return clone_();}

virtual ~IClonable() {}
protected:
virtual IClonable *clone_() const = 0;
};

template<class Base>
class IClonable: public IClonable<void> {

public:
Base *clone() const {return static_cast<Base *>(clone_());}
};

Je vidět, že clone už není virtuální, ale deleguje svoji činnost na chráněnou metodu clone_(). To proto, aby nám překladač nevnucoval kontrolu, zda je návratová hodnota kovariantní s IClonable. Použití šablony je jednoduché

class IBase: public IClonable<IBase> {
public:
};

Opakovat deklaraci clone() zde není potřeba, to už zařídila šablona.

Implementace klonování

Je to všechno krásné, pokud si to takhle hezky navrhneme, ale narazíme na jeden problém. Klonování je třeba implementovat. Přesněji, musí jej implementovat každý potomek, který lze klonovat. Implementovat musíme klonování i na každé úrovni. Pouze abstraktní třídy neimplementují klonování.

Jinými slovy, každý potomek na libovolné úrovni vnoření musí mít implementovanou metodu

virtual IClonable *clone_() const {...};

Implementace je naštěstí jednoduchá, obsahuje vlastně jen jednu operaci

class Derived: public IBase {
public:
virtual IClonable *clone_() const {return new Derived(*this)};
...
}

a pokud chceme vracet typ Derived, tak i metodu clone() /*bez podtržítka*/. Překladač nám tady nijak nepomůže. Klonování je potřeba implementovat opravdu všude, a pokud někde zapomeneme, výsledkem klonování bude první rodičovská třída, která implementuje clone(). Pokud je to náhodou IBase, tak je to ještě dobrý, protože nám to překladač zpravidla řekne (abstraktní). Ale na jiných úrovních dědění to může být docela zrada.

Tady se zase ozve "lenost". Při rozvětvené hierarchii se z neustálého kopírování metody clone_() brzo zblázníme. Můžeme si samozřejmě pomoci vhodným _makrem_, ale tomu bych se pro tentokrát vyhnul z odkazem na to, že makra jsou "evil"

Tak jak si to zjednodušit bez maker? Že by zase šablony?

Napíšeme si šablonu, která bude vždy stát mezi base třídou a derived třídou. Takové mezipatro... Tato šablona bude za nás implementovat vše, co by nás při návrhu jenom obtěžovalo. "Mezipatrová" šablona zpravidla vypadá takto:

template<class Base, class Derived>
class Mezipatro: public Base {...
....
};

Použití mezipatra pak vypadá takto

class Derived: public Mezipatro<Base,Derived>

Mezipatro dostává jako parametr jméno Base třídy, to proto, aby ji mohlo podědit. Zároveň dostává jméno Derived třídy, aby mohlo přistupovat k datům z Derived třídy. Mezipatro je designováno namíru Derived třídy. Je to podobné jako u Invokeru s tím rozdílem, že Invoker zpravidla nespolupracuje s Base třídou.

Naše mezipatro pro klonování nazveme Clonable (bez I), tím budem označovat všechny třidy, jejichž instance jsou klonovatelné.

template<class Base, class Derived> 
class Clonable: public Base {
public:
virtual IClonable *clone() const {
const Derived *dd = static_cast<const Derived *>(this);
return new Derived(*dd);
}
Derived *clone() const {return static_cast<Derived *>(clone_());}
};

Mezipatro mívá problémy s konstruktory. Netuší totiž nic o parametrech, které Derived třída předává do Base třídy pomocí konstruktoru. Musí však zajistit jejich předání, takže musí minimálně obsahovat stejný počet parametrů a stejných typů, jako konstruktory třídy, které dědí. To se velice špatně generizuje (řešení přinese až C++x0), ale dá se to uděla pro omezený počet parametrů. dejmetomu maximálně 10

       
Clonable() {}
template<typename T1>
Clonable(const T1 &t1)
:Base(t1) {}

template<typename T1,typename T2>
Clonable(const T1 &t1,const T2 &t2)
:Base(t1,t2) {}

template<typename T1,typename T2,typename T3>
Clonable(const T1 &t1,const T2 &t2,const T3 &t3)
:Base(t1,t2,t3) {}

.... atd...

Tímto způsobem jsme schopni přesměrovat parametry jdoucí z Derived do Mezipatra dál do Base třídy 1:1. Optimalizující překladač dokonce parametry přes mezipatro nepřeposílá, ale předá je rovnou do Base

Poslední vylepšení klonující třídy je definice Super, která nám zjednodušuje zápis volání nadřazeného konstruktoru:

typedef Clonable<Base, Derived> Super;

Tady máme celý zápis

template<class Base, class Derived>
class Clonable: public Base {
public:
Clonable() {}

template<typename T1>
Clonable(const T1 &t1)
:Base(t1) {}

template<typename T1,typename T2>
Clonable(const T1 &t1,const T2 &t2)
:Base(t1,t2) {}

template<typename T1,typename T2,typename T3>
Clonable(const T1 &t1,const T2 &t2,const T3 &t3)
:Base(t1,t2,t3) {}

template<typename T1,typename T2,typename T3, typename T4>
Clonable(const T1 &t1,const T2 &t2,const T3 &t3, const T4 &t4)
:Base(t1,t2,t3,t4) {}

template<typename T1,typename T2,typename T3, typename T4, typename T5>
Clonable(const T1 &t1,const T2 &t2,const T3 &t3, const T4 &t4, const T5 &t5)
:Base(t1,t2,t3,t4.t5) {}

template<typename T1,typename T2,typename T3, typename T4, typename T5,
typename T6>
Clonable(const T1 &t1,const T2 &t2,const T3 &t3, const T4 &t4, const T5 &t5,
const T6 &t6)
:Base(t1,t2,t3,t4.t5,t6) {}

template<typename T1,typename T2,typename T3, typename T4, typename T5,
typename T6,typename T7>
Clonable(const T1 &t1,const T2 &t2,const T3 &t3, const T4 &t4, const T5 &t5,
const T6 &t6,const T7 &t7)
:Base(t1,t2,t3,t4.t5,t6,t7) {}

template<typename T1,typename T2,typename T3, typename T4, typename T5,
typename T6,typename T7, typename T8>
Clonable(const T1 &t1,const T2 &t2,const T3 &t3, const T4 &t4, const T5 &t5,
const T6 &t6,const T7 &t7,const T8 &t8)
:Base(t1,t2,t3,t4.t5,t6,t7,t8) {}

template<typename T1,typename T2,typename T3, typename T4, typename T5,
typename T6,typename T7, typename T8, typename T9>
Clonable(const T1 &t1,const T2 &t2,const T3 &t3, const T4 &t4, const T5 &t5,
const T6 &t6,const T7 &t7,const T8 &t8, const T9 &t9)
:Base(t1,t2,t3,t4.t5,t6,t7,t8,t9) {}

template<typename T1,typename T2,typename T3, typename T4, typename T5,
typename T6,typename T7, typename T8, typename T9, typename T10>
Clonable(const T1 &t1,const T2 &t2,const T3 &t3, const T4 &t4, const T5 &t5,
const T6 &t6,const T7 &t7,const T8 &t8, const T9 &t9, const T10 &t10)
:Base(t1,t2,t3,t4.t5,t6,t7,t8,t9,t10) {}


virtual IClonable *clone_() const {
const Derived *dd = static_cast<const Derived *>(this);
return new Derived(*dd);
}

Derived *clone() const {return static_cast<Derived *>(clone_());}

typedef Clonable<Base, Derived> Super;
};

A použití


class Test_Base: public IClonable<Test_Base> {
public:
virtual void printSelf() {
printf("This is base class\n");
}
};

class Test_Derived1: public Clonable<Test_Base,Test_Derived1> {
protected:
int value;
public:
Test_Derived1(int val):value(val) {}
virtual void printSelf() {
printf("This is Derived1 class, and value is %d\n", value);
}
};

class Test_Derived2: public Clonable<Test_Derived1,Test_Derived2> {
protected:
int value;
std::string text;
public:
Test_Derived2(int val, std::string text):Super(val),text(text) {}
virtual void printSelf() {
printf("This is Derived2 class, and value is %d and text is %s\n",
value, text.c_str());
}
};

V posledním příkladě si všimněte použití typu Super.

... a příklad klonování

int main(int argc, char **argv) {
Test_Derived1 a(10);
Test_Derived2 b(20,"ahoj");

std::auto_ptr<Test_Base> x(a.clone());
std::auto_ptr<Test_Base> y(b.clone());

x->printSelf();
y->printSelf();

}

Závěr

Překladač C++ automatické klonování nepodporuje a tak si musíme vypomoci sami. Říká se, že lenost je hybnou silou pokroku. Zde to rozhodně není jinak. Lenost nás nutí vymýšlet jednodušší způsoby zápisu, aby naše myšlení mohlo ušetřený čas věnovat skutečnému problémů.

Mé "polo-automatické" řešení klonování vyžaduje pouze jiný zápis dědění a o víc se člověk nemusí starat. Upřímě si myslím, že k automatickému klonování to nemá opravdu daleko.

vytvořeno: 27.7.2008 02:50:49, změněno: 27.7.2008 02:50:49
Jsou informace v článku pro Vás užitečné?
  • (2)
  • (0)
  • (0)
  • (0)
  • (0)

Podobné články

Automatické klonování objektů v C++ II

Dneska se podíváme jak klonovat pomocí clone_ptr (ClonePtr)

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

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

GarbageCollector v C++, prototyp

Neustále slýchám, jak v C++ chybí Garbage Collector. Ač si o tomto nástroji myslím své, a v zásadě jsem proti zavedení GC v C++, přijal jsem tyto nářky jako výzvu.

Funkce s volitelným počtem argumentů v C++

Leckterý programátor v C jistě zná syntaxtický zápis funkce s volitelným počtem argumentů. V následujícím článku se podíváme na nevýhody tohoto nástroje a způsoby jak to implementovat jinak

Efektivní alokátor malých objektů III

Třetí díl seriálu o alokaci paměti. Předchozí díl Efektivní alokátor malých objektů II
Reklama: