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++ II - Bredyho blog - Ondřej Novák
Bredyho blog - Ondřej Novák

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

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


V Boost knihovně, ale i na celém internetu (zdroj google) je pro účely klonování populární clone_ptr. Co to je a jak to funguje nejlépe přiblížíme tím, že si jej zkusíme naimplementovat. Protože název "clone_ptr" odporuje pravidlům pro názvy tříd v knihovně BredyLibs (LightSpeed), budeme psát třídu ClonePtr

ClonePtr

ClonePtr je třída představující chytrý ukazatel. Zapisuje se ClonePtr<třída>, kde třída je jméno nějaké třídy, na jejíž instanci ukazatel ukazuje. Chtrý ukazatel obsahuje všechny běžné operace dereference, jako -> a *.

Chytrý ukazatel pracuje podobně jako copy_ptr, tedy automaticky dealokuje destruktorem svěřený objekt a při kopírování chytrého ukazatele provádí kopii svěřeného objektu. Na rozdíl od copy_ptr ale provádí úplnou kopii, tedy, pokud chytrý ukazatel ukazuje na instancí potomka, provede kopii potomka, tedy tak jak má klonování fungovat.

Jak je tohle implementované?

Hlavním prvkem implementace je konstruktor jako šablona


template<class U>
ClonePtr(U *ptr)

Tedy, aby ClonePtr fungoval, musíme mu dodat ukazatel té třídy, kterou chceme klonovat, nikoliv base třídy. Ideálně takto

ClonePtr<BaseTrida> p(new DerivedTrida(...));

Ze znalosti U dokážeme vytvořit klonovací funkci. A aby jsme se nemuseli spoléhat na virtuální destruktory, tak také vytvoříme likvidující funkci. Využijeme k tomu abstraktní rozhraní, protože potřebujeme pozdní vazbu.

 class IOperations {
public:
virtual T *clone(const T *src) const = 0;
virtual void destroy(T *src) const = 0;
};

Vidíme funkci clone a funkci destroy. Obě vyžadují ukazatel instanci jako T, což je v tuto chvílí BaseTřída. Obě funkce si musí z ukazatel vytvořit ukazatel na "svou" DerivedTřídu. Implementace odvozená od template<class U> bude vypadat takto:

class Opers: public IOperations {
public:
virtual T *clone(const T *src) const {
return new U(*static_cast<const U *>(src));
}
virtual void destroy(T *src) const {
delete static_cast<U *>(src);
}
};

Překladač nám vygeneruje tolik variant rozhraní IOperations, kolik tříd používá ClonePtr. Stačí tedy, aby při konstrukci ClonePtr byl uložen nejen vlastní ukazatel, ale proměnná ukazující na IOperations, implementující obě operace.

Pak už stačí, aby ke klonování provedl ClonePtr funkci clone a jako parametr dodal svěřený ukazatel. Výsledkem je nový ukazatel ukazující na novou kopii objektu. Stejně tak destruktor ClonePtr bude pro zničení objektu volat metodu delete. Tím mimojiné zaručíme zavolání správného destruktoru bez ohledu, zda base třída obsahuje, či neobsahuje virtuální destruktor.

Listing

template<class T>
class ClonePtr {


typedef T *(* CloneFunct)(const T *src);
T *ptr;

class IOperations {
public:
virtual T *clone(const T *src) const = 0;
virtual void destroy(T *src) const = 0;
};


const IOperations *operations;

template<class U>
const IOperations *getOperations(const U *fromPtr) {

class Opers: public IOperations {
public:
virtual T *clone(const T *src) const {
return new U(*static_cast<const U *>(src));
}
virtual void destroy(T *src) const {
delete static_cast<U *>(src);
}
};

static Opers opers;
return &opers;
}

const IOperations *nullOperations() {

class Opers: public IOperations {
public:
virtual T *clone(const T *src) const {
return 0;
}
virtual void destroy(T *src) const{
}
};

static Opers opers;
return &opers;
}

public:

ClonePtr():ptr(0), operations(nullOperations()) {}
template<class U>
ClonePtr(U *ptr):ptr(ptr),operations(getOperations(ptr)) {}
ClonePtr(const ClonePtr<T> &other)
:ptr(other.operations->clone(other.ptr))
,operations(other.operations) {}
~ClonePtr() {operations->destroy(ptr);}
ClonePtr &operator=(const ClonePtr<T> &other) {
operations->destroy(ptr);
ptr = other.operations->clone(other.ptr);
operations = other.operations;
return *this;
}

T &operator *() {return *ptr;}
T *operator->() {return ptr;}
const T *operator->() const {return ptr;}
operator T *() {return ptr;}
operator const T *() const {return ptr;}

};

Použití

 //Test_Base a Test_Derived z předchozího dílu
ClonePtr<Test_Base> bb(new Test_Derived1(30));
ClonePtr<Test_Base> cc(new Test_Derived2(40,"cau"));
ClonePtr<Test_Base> dd(bb);
ClonePtr<Test_Base> ee(cc);


dd->printSelf();
ee->printSelf();

Závěr

ClonePtr je celkem pěknou alternativou k poloautomatickému klonování pomocí metody clone(). Funguje totiž naprosto automaticky. Nevýhodou je, že chytrý ukazatel je větší, kromě vlastního ukazatele si musí pamatovat odkaz na rozhraní implementující obě funkce. Na druhou stranu, ClonePtr zvládá klonovat i hierarchie, které neobsahují virtuální destruktory. Není to sice doporučené, ale zejména malé třídy často virtualní destruktory nepíší.

Takovou největší nevýhodou je nutné držet ukazatel v chytrým ukazateli. Jakmile odněkud vypadně obyčjný ukazatel, ClonePtr nám nepomůže. Musí nám dorazit chytrý ukazatel ClonePtr inicializovaný správně ... i s typem uloženého objektu. Tam kde nemáme možnost tohle změnit nám ani ClonePtr nepomůže. V třetím díle si ukážeme, že i tohle se dá vyřešit.

vytvořeno: 28.7.2008 01:48:31, změněno: 28.7.2008 01:48:31
Jsou informace v článku pro Vás užitečné?
  • (1)
  • (0)
  • (0)
  • (0)
  • (0)

Podobné články

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í.

Ú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: