6.Template

Cele mai moderne compilatoare C++ suporta un 'super-macro-mecanism' care permite programatorului sa defineasca functii sau clase generice. Acestea devin concrete odata ce codul lor este utilizat de entitati reale. Termenul general pentru asemenea functii (clase) este templat-uri.

7.1. Functii template

Definitia unei functii template este foarte asemanatoare cu cea a unei functii concrete, cu exceptia faptului ca argumentele functiei sīnt numite īntr-un mod simbolic. Exemplu:

template <class T>

void swap(T &a, T &b)

{

T

tmp = a;

a = b;

b = tmp;

}

Īn acest exemplu este definita o functie template swap(), ce actioneaza asupra oricarui tip de obiecte, atīt timp cīt aceste obiecte pot fi asignate unul altuia si pot fi initializate. Tipul general al argumentelor este numit T, si este dat īn prima linie a functiei. Codul efectueaza urmatoarele operatii:

. Pentru īnceput, se creeaza o variabila de tip T (tmp) si este initializat cu argumentul a.

. Apoi se interschimba variabilele a si b, prin intermediul variabilei locale tmp.

Sa notam ca definitia unei functii template este similara cu #define īn sensul ca functia template nu este codata īnca; devine cod numai īn momentul utilizarii. Un exemplu de apel al functiei:

int main()

{

int

a = 3,

b = 16;

double

d = 3.14,

e = 2.17;

Person

k("Karel", "Rietveldlaan 37", "5426044"),

f("Frank", "Oostumerweg 17", "4032223");

swap(a, b);

printf("a = %d, b = %d\n", a, b);

swap(d, e);

printf("d = %lf, e = %lf\n", d, e);

swap(k, f);

printf("k's name = %s, f's name = %s\n",

k.getname(), f.getname());

return (0);

}

Odata ce compilatorul C++ detecteaza utilizarea functiei swap(), se genereaza si codul concret. Īn cazul nostru se creeaza trei functii, una pentru argumentul int, alta pentru double si a treia pentru Person. Compilatorul va genera si nume diferite pentru aceste functii (nume interne), ca de exemplu swap_int_int(), swap_double_double(), swap_Person_Person(). De notat ca īn cazul ultimei functii, pentru utilizarea ei este necesar ca īn clasa Person sa fie definit un constructor copy si redefinit operatorul de asignare. Faptul ca compilatorul genereaza codul numai īn momentul executiei are o consecinta importanta: definitia functiei template nu poate fi inclusa īntr-o biblioteca, ci numai īntr-un fisier header.

7.2. Clase template

'Super-macro-mecanismul' oferit de compilator poate fi utilizat si pentru o clasa generica, utilizabila pentru orice tip de entitate. Īn mod obisnuit, clasele template sīnt clase container (de stocaj) si reprezinta tablouri, liste, stive sau arbori.

Clasa template Array

Īn exemplul urmator vom prezenta clasa template Array, ce va putea fi folosita pentru stocarea de matrici continīnd orice elemente:

#include <stdio.h>

#include <stdlib.h>

template<class T>

class Array

{

public: // constructori, destructori

virtual ~Array(void) { delete [] data; }

Array(int sz = 10) { init(sz); }

Array(Array<T> const &other);

Array<T> const &operator=(Array<T> const &other);

// interfata

int size() const;

T &operator[](int index);

private: // date

int n;

T *data;

void init(int sz); // initializatorul

};

template <class T>

void Array<T>::init(int sz)

{

if (sz < 1)

{

fprintf(stderr, "Array: cannot create array of size < 1\n"

" requested: %d\n", sz);

exit(1);

}

n = sz;

data = new T[n];

}

template <class T>

Array<T>::Array(Array<T> const &other)

{

n = other.n;

data = new T[n];

for (register int i = 0; i < n; i++)

data[i] = other.data[i];

}

template <class T>

Array<T> const &Array<T>::operator=(Array<T> const &other)

{

if (this != &other)

{

delete []data;

n = other.n;

data = new T[n];

for (register int i = 0; i < n; i++)

data[i] = other.data[i];

}

return (*this);

}

template <class T>

int Array<T>::size() const

{

return (n);

}

template <class T>

T &Array<T>::operator[](int index)

{

if (index < 0 || index >= n)

{

fprintf(stderr, "Array: index out of bounds, must be between"

" 0 and %d\n" " requested was: %d\n",

n - 1, index);

exit(1);

}

return (data[index]);

}

Referitor la codul precedent, facem urmatoarele observatii:

. Definitia clasei īncepe cu template <class T>, similara cu definitia functiei template: aceasta linie contine numele simbolic T, ce se refera la tipul ce va fi utilizat de clasa.

. Īn definitia clasei, toate functiile care contin ca argument un Array vor face referinta la acest argument ca Array<T>.

. Īn definitiile functiilor, numele clasei este referit tot ca Array<T>. Motivul este faptul ca compilatorul va modifica numele clasei Array īn momentul īn care ea va fi utilizata concret. Numele simbolic T va deveni atunci parte din noul nume al clasei.

Īn ceea ce priveste clasa propriu-zisa, remarcam faptul ca:

. Clasa utilizeaza doua date membru: un pointer pentru un tablou alocat (data) si o dimensiune a tabloului (n).

. Clasa contine un constructor copy, un destructor (virtual) si o functie de asignare reacoperita, deoarece se adreseaza memoriei alocate.

. Instructiunea delete [] data din destructor si din functia de asignare asigura faptul ca destructorul obiectelor din tabloul pointat de data este apelat īnaintea dealocarii zonei.

. Instructiunea data [i] = other.data [i] din functia de asignare copie datele dintr-un alt tablou. Instructiunea fie va copia memoria bit cu bit, fie va activa operatorul de asignare din clasa obiectelor stocate (ca īn clasa Person).

Īn ceea ce priveste clasele template, īn general, compilatorul va trebui sa le cunoasca īn momentul compilarii, adica īntregul cod va trebui pus īntr-un fisier header (array.h) care va fi inclus īn fisierul sursa īn care se va utiliza clasa. Folosirea clasei tempate Array este prezentata īn continuare:

#include <stdio.h>

#include "array.t"

#define PI 3.1415

int main()

{

Array<int> intarr;

for (register int i = 0; i < intarr.size(); i++)

intarr[i] = i << 2;

Array<double> doublearr;

for (i = 0; i < doublearr.size(); i++)

doublearr[i] = PI * (i + 1);

for (i = 0; i < intarr.size(); i++)

printf("intarr[%d] : %d\n"

"doublearr[%d]: %g\n",

i, intarr[i],

i, doublearr[i]);

return (0);

}

Remarcam faptul ca tipul actual de tablou trebuie specificat explicit īn momentul definirii unui obiect din clasa template. Clasa Array poate fi utilizata atīt timp cīt se poate aloca memorie si cīt entitatile din tablou pot fi asignate. Aceasta īnseamna ca pentru clasa Person, de exemplu, este nevoie de un constructor implicit si de o functie de asignare:

int main()

{

Array<Person> staff(2); // tablou de doua persoane

Person one, two;

. // cod pentru asignarea numelui, adresei si telefonului

staff[0] = one;

staff[1] = two;

printf("%s\n%s\n",

staff[0].getname(), staff[1].getname());

return (0);

}

Deoarece tabloul staff contine obiecte Person, functiile de interfata ca getname() vor fi folosite pentru apelarea elementelor din tablou.

Un alt mod de implementare a unei clase de stocaj

O sarcina foarte des īntīlnita īn multe programe este cea de stocare a datelor, urmata apoi de sortarea, selectarea lor, etc. Datele stocate pot fi fie simpli īntregi, fie date complexe (asa cum face sistemul de operare). Conform principiului programarii orientate obiect, vom dezvolta doua clase: o clasa Storage, ce va stoca obiectele, si o clasa Storable, ce va contine prototipul obiectelor ce vor fi stocate. Īn ceea ce priveste functionalitatea clasei Storage, ea va trebui cel putin sa poata sa adauge obiecte si sa extraga obiecte (din zona de stocare). De asemeni va trebui sa poata furniza si numarul obiectelor stocate. Partea de date va contine un tablou dinamic (pointeri la obiectele stocate). Organizarea interna poate fi descrisa astfel:

Functiile interfata pentru clasa Storage

Interfata clasei este continuta īn trei functii:

. Functia add(Storable const *newobj) adauga un obiect la zona de stocaj. Functia realoca pointerii din tablou pentru a permite inserarea adresei noului obiect.

. Functia Storable const *get(int index) īntoarce un pointer la obiectul care este stocat de catre pointerul nr. index.

. Functia int nstored() īntoarce numarul obiectelor din zona de stocaj

A copia sau a nu copia ?

Exista doua moduri distincte de a descrie functia add(), īn functie de faptul daca obiectele stocate sīnt copii ale originalelor sau obiectele īn-sine. Cu alte cuvinte, functia add() doar stocheaza adresa obiectului (īn tabloul de pointeri) sau face mai īntīi o copie a lui si apoi retine adresa (copiei) ? Intrebarea nu este triviala. Sa consideram urmatorul exemplu:

Storage store;

Storable something;

store.add(something); // adauga īn stocaj

// presupunem ca este definit Storable::modify()

something.modify(); // modifica obiectul original

Storable

*retrieved = store.get(0); // extrage din stocaj

// īn acest moment "*retrieved" este egal cu "something" ?!

Daca stocam adresele initiale, raspunsul la ultima īntrebare va fi da. Daca stocam adresele copiilor, raspunsul va fi nu. Aceasta ultima abordare este necesara cīnd dorim sa 'salvam' obiectele, pentru a putea fi regasite daca dintr-un motiv sau altul obiectul initial este modificat. Aceasta ultima abordare va fi aleasa īn continuare.

Cine face copia ?

Stocarea copiilor nu reprezinta o problema deosebita. Dar daca vrem ca aceasta clasa, Storage, sa fie cīt mai generala, va trebui sa renuntam a include o functie ce copie obiectele, deoarece nu stim dinainte ce fel de obiecte vom folosi. O abordare naiva, de genul:

void Storage::add(Storable const *obj)

{

Storable

*to_store = new *obj;

// adauga to_store īn locul argumentului obj

.

.

}

nu va functiona. Codul īncearca sa faca o copie a lui obj folosind operatorul new, care īn fond va apela constructorul copy din clasa Storable. Totusi, daca Storable este doar o clasa de baza, iar obiectul propriu-zis este dintr-o clasa derivata (fie ea Person), cum va putea constructorul clasei Storable sa creeze o copie a unui obiect Person ? Deci clasa obiectelor de stocat va trebui sa contina o functie care duplica un obiect si īntoarce un pointer la dublura lui. Daca denumim functia duplicate(), codul functiei add() devine:

void Storage::add(Storable const *obj)

{

Storable

*to_store = obj->duplicate();

// acum adauga to_store īn loc de obj

.

.

}

Functia duplicate() este apelata prin folosirea unui pointer la obiectul original (pointerul obj). Clasa Storable din exemplul precedent este doar o clasa de baza care defineste un protocol, si nu clasa propriu-zisa a obiectelor stocate. Deci functia duplicate() nu trebuie implementata īn Storable, ci numai īn clasele derivate. Cu alte cuvinte, functia duplicate() este o functie pur virtuala.

Clasa Storable

Folosind aceeasi abordare putem defini acum clasa Storable. Va trebui sa raspundem la urmatoarele īntrebari:

. Clasa Storable are nevoie numai de un constructor implicit, sau si de alti constructori (copy, de exemplu) ?. Raspunsul este nu, deoarece clasa este doar o clasa de baza.

. Are nevoie clasa de un destructor ? Acesta trebuie sa fie (pur) virtual ? Raspunsul este da. Destructorul va fi apelat īn momentul īncetarii existentei unui obiect Storable. Este foarte posibil ca clasele derivate sa aiba proprii destructori, ceea ce impune un destructor virtual īn Storable (cīnd obiectul pointat de Storable * este distrus va fi apelat destructorul actual al clasei obictului concret). Totusi, nu ar trebui sa fie pur virtual, deoarece e posibil ca unele clase derivate din Storable sa nu aiba nevoie de destructori. Daca este pur virtual, īn clasa derivata tebuie explicit definit un destructor fara instructiuni.

Definitia clasei si a functiilor este data īn continuare:

class Storable

{

public:

virtual ~Storable();

virtual Storable *duplicate() const = 0;

};

Storable::~Storable()

{

}

Conversia unei clase existente la clasa Storable

Pentru a arata practic acest lucru, sa consideram clasa Person. Ea va fi rescrisa īn conformitate cu protocolul clasei Storable (este prezentat numai codul relevant sau nou):

class Person: public Storable

{

Person(Person const &other); // constructorul copy

Person const &operator=(Person const &other); // asignarea

Storable *duplicate() const; // funtia de duplicare

.

.

}

Īn implementarea functiei Person::duplicate() vom putea folosi fie constructorul copy, fie constructorul implicit īmpreuna cu operatorul de asignare reacoperit:

// prima versiune:

Storable *Person::duplicate() const

{

// foloseste constructorul implicit īn new Person

Person *dup = new Person;

*dup = *this; // foloseste operatorul de asignare īn *dup = *this

return (dup);

}

// a doua versiune:

Storable *Person::duplicate() const

{

return (new Person(*this)); // foloseste constructorul copy īn new Person(*this)

}

O asemenea conversie a clasei Person pentru a corespunde nevoilor clasei Storable presupune ca sursa ce contine codul initial este disponibila si poate fi modificata. Totusi, chiar daca definitia clasei Person nu este disponibila, ci continuta īntr-o biblioteca, conversia la formatul clasei Storable nu este dificila:

class StorablePerson: public Person, public Storable

{

public: // functia de duplicare

Storable *duplicate() const;

};

Storable *StorablePerson::duplicate() const

{

return (new *(Person*)this);

}

Clasa Storage

Acum putem implementa clasa Storage:

class Storage: public Storable

{

public: // destructori, constructori

~Storage();

Storage();

Storage(Storage const &other);

// reacoperirea asignarii

Storage const &operator=(Storage const &other);

// functia de duplicare

Storable *duplicate() const;

// interfata

void add(Storable *newobj);

int nstored() const;

Storable *get(int index);

private: // primitivele copy/destroy

void destroy();

void copy(Storage const &other);

// datele private

int n;

Storable **storage;

};

Facem urmatoarele observatii:

. Clasa contine un constructor copy si o reacoperire a operatorului de asignare, necesare datorita faptului ca īn clasa Storage exista pointeri.

. Clasa Storage este la rīndul ei derivata din Storable. Aceasta īnseamna ca obiectele de tip Storage pot fi la rīndul lor stocate īntr-o zona de stocaj, creīndu-se un 'super-stocaj' (de exemplu, o lista de grupuri de persoane).

. Cele doua functii private copy si destroy() vor fi discutate putin mai tīrziu.

Implementarea destructorului, a constructoruliui si a functiei de asignare:

// constructorul implicit

Storage::Storage()

{

n = 0;

storage = 0;

}

// constructorul copy

Storage::Storage(Storage const &other)

{

copy(other);

}

// destructorul

Storage::~Storage()

{

destroy();

}

// reacoperirea asignarii

Storage const &Storage::operator=(Storage const &other)

{

if (this != &other)

{

destroy();

copy(other);

}

return (*this);

}

Functiile primitive copy() si destroy() copie neconditionat un alt obiect Storage sau distrug, tot neconditionat, continutul obiectului curent. Sa observam faptul ca copy() apeleaza duplicate() pentru a duplica obiectele stocate:

void Storage::copy(Storage const &other)

{

n = other.n;

storage = new Storable* [n];

for (int i = 0; i < n; i++)

storage [i] = other.storage [i]->duplicate();

}

void Storage::destroy()

{

for (register int i = 0; i < n; i++)

delete storage [i];

delete storage;

}

Functia duplicate(), necesara deoarece Storage este la rīndul sau un Storable, foloseste constructorul copy pentru duplicarea obiectului curent:

Storable *Storage::duplicate() const

{

return (new *this);

}

Īn final, prezentam functiile interfata care adauga obiectele īn stocaj, le regasesc sau determina numarul obiectelor stocate:

void Storage::add(Storable const *newobj)

{

storage = (Storable **) realloc(storage,

(n + 1) * sizeof(Storable *)); // realoca tabloul de stocaj

// pune copia lui newobj īn storage

storage [n] = newobj->duplicate();

n++; // creste numarul de obiecte din stocaj

}

Storable *Storage::get(int index)

{

if (index < 0 || index >= n) // verifica daca indexul este īn afara rangului

return (0);

return (storage [index]); // īntoarce adresa obiectului stocat

}

int Storage::nstored() const

{

return (n);

}

7.2. Arbore binar

Aceasta sectiune va prezenta implementarea unui arbore binar īn C++. Analog cu clasele Storage si Storable, doua clase separate vor fi folosite: una va reprezenta arborele iar cealalta obiectele ce vor fi stocate īn arbore. Ele vor fi numite, logic, Tree si Node.

Clasa Node

Clasa Node este o clasa abstracta (pur virtuala), care defineste protocolul pentru utilizarea claselor derivate īmpreuna cu clasa Tree.

. Cīnd datele sīnt stocate īntr-un arbore binar, locul exact de plasare a unei date este determinat de o anumita ordine: deci va trebui determinat modul īn care se va face sortarea lor, ceea ce implica o comparatie īntre obiecte. Functia de comparatie va trebui sa informeze functia apelanta (cea care plaseaza obiectul) daca obiectul īn chestiune este mai 'mic' sau mai 'mare' ca altul. Comparatia tine de clasa Node: arborele nu poate 'sti' ce obiecte sīnt memorate īn el. Deci partea de protocol ce se va gasi īn Node este virtual int compare(Node const *other) const = 0;. Functia de comparare va fi implementata īn fiecare clasa derivata.

. Similar cu clasa Storage, clasa Tree va contine copii ale obiectelor. Responsabilitatea duplicarii unui obiect este legata strict de clasa Node, unde va fi definita o functie pur virtuala: virtual Node *duplicate() const = 0;

. Īn timpul procesarii unui arbore binar, arborele este parcurs recursiv, efectuīndu-se o operatie specifica fiecarui obiect stocat. Operatia depinde de tipul actual al obiectelor. Prin declararea unei functii pur virtuale: virtual void process() = 0; īn clasa Node, responsabilitatea procesarii efective cade īn sarcina claselor derivate.

. Īn momentul stocarii unui obiect īn arbore, se poate īntīmpla ca acel obiect sa fi fost deja stocat. Pentru prevenirea dublei stocari, vom defini o functie virtuala already_stored() (dar nu pur virtuala), care implicit nu va face nimic: virtual void already_stored(); Ea va putea fi redefinita īn clasele derivate

Definitia completa si declaratia clasei Node este data īn continuare:

class Node

{

public:

virtual ~Node(); // destructorul

virtual Node* duplicate() const = 0; // duplicatorul

virtual int compare(Node const *other) const = 0; // compararea a 2 obiecte

virtual void process() = 0; // functia necesara procesarii unui nod

virtual void already_stored(); // verifica daca a fost deja stocat

};

Node::~Node()

{

}

void Node::already_stored()

{

}

Clasa Tree

Clasa Tree este responsabila pentru stocarea obiectelor derivate din Node. Pentru implementarea structurii recursive a arborelui, clasa Tree contine doi pointeri, unul pointīnd spre subarborele stīng, Tree *left, si unul pointīnd spre subarborele drept, Tree *right. Informatia care este stocata īntr-un nod este reprezentata sub forma Node *info. Pentru scanarea unui arbore binar, clasa Tree ofera trei metode: preordine, inordine si postordine. Scanarea īn preordine īnseamna vizitarea mai īntīi a nodului curent, apoi a subarborelui stīng, apoi a subarborelui drept. Scanarea īn inordine īnseamna scanarea mai īntīi a subarborelui stīng, apoi a nodului curent si īn final al subarborelui drept. Iar scanarea īn postordine īnseama pentru īnceput subarborele stīng, apoi subarborele drept iar īn final nodul curent. Definitia clasei Tree este data īn continuare:

class Tree

{

public: // destructori, constructori

~Tree();

Tree();

Tree(Tree const &other);

Tree const &operator=(Tree const &other); // asignarea

void add(Node *what); // adaugarea unui Node

// procesarea arborelui cu cele trei metode

void preorder_walk();

void inorder_walk();

void postorder_walk();

private:

// primitivele copy si destroy

void copy(Tree const &other);

void destroy();

// datele

Tree *left, *right;

Node *info;

};

Functiile 'standard'

Asa dupa cum se poate observa, clasa Tree contine trei cīmpuri pointeri. Apare deci necesitatea definirii unui destructor, a unui constructor copy si a reacoperii operatorului de asignare. Ele vor fi implementate cu ajutorul primitivelor copy() si destroy(), prezentate ulterior:

// destructorul: distruge arborele

Tree::~Tree()

{

destroy();

}

// constructorul implicit: initializeaza cu 0

Tree::Tree()

{

left = right = 0;

info = 0;

}

// constructorul copy : initializeaza continutul unui alt obiect

Tree::Tree(Tree const &other)

{

copy(other);

}

// reacoperirea asignarii

Tree const &Tree::operator=(Tree const &other)

{

if (this != &other)

{

destroy();

copy(other);

}

return (*this);

}

Memorarea unui obiect īn arbore

Adaugarea unui obiect īn arbore este un proces recursiv. Īn momentul apelului functiei add(), apar de fapt cīteva posibilitati:

. Cīmpul info din nodul curent este un pointer NULL. Īn acest caz o copie a obiectului este inserata īn nodul curent

. Daca arborele este partial 'umplut', este necesar sa se stabileasca daca obiectul de adaugat este 'īnaintea' sau 'dupa' obiectul din nodul curent. Comparatia este facuta de functia compare(). Īn functie de raspuns, inserarea se va face fie īn subarborele stīng, fie īn subarborele drept (acesti subarbori vor trebui mai īntīi alocati).

. Daca comparatia precedenta da raspunsul 'egal', obiectul nu va trebui inserat īn arbore. Īn acest caz se va apela already_stored().

Functia add():

void Tree::add(Node *what)

{

if (! info)

info = what->duplicate();

else

{

register int

cmp = info->compare(what);

if (cmp < 0)

{

if (! left)

{

left = new Tree;

left->info = what->duplicate();

}

else

left->add(what);

}

else if (cmp > 0)

{

if (! right)

{

right = new Tree;

right->info = what->duplicate();

}

else

right->add(what);

}

else

info->already_stored();

}

}

Scanarea unui arbore

Clasa Tree ofera trei metode de scanare a arborelui: preordine, inordine si postordine. Functiile vor fi recursive.

void Tree::preorder_walk()

{

if (info)

info->process();

if (left)

left->preorder_walk();

if (right)

right->preorder_walk();

}

void Tree::inorder_walk()

{

if (left)

left->inorder_walk();

if (info)

info->process();

if (right)

right->inorder_walk();

}

void Tree::postorder_walk()

{

if (left)

left->postorder_walk();

if (right)

right->postorder_walk();

if (info)

info->process();

}

Primitivele copy() si destroy()

Functiile copy() si destroy() sīnt doi membri private ce implementeaza operatiile primitive īn clasa Tree: copierea continutului unui alt arbore si distrugerea unui arbore:

void Tree::destroy()

{

delete info;

if (left)

delete left;

if (right)

delete right;

}

void Tree::copy(Tree const &other)

{

info = other.info ? other.info->duplicate() : 0;

left = other.left ? new Tree(*other.left) : 0;

right = other.right ? new Tree(*other.right) : 0;

}

Cīteva observatii:

. Functia destroy() este recursiva, desi nu pare la prima vedere: instructiunea delete left va activa destructorul obiectului Tree pointat de left, care la rīndul lui va apela destroy(), etc.

. Īn mod analog, functia copy() este la rīndul ei recursiva. Instructiunea left = new Tree (*other.left) va activa constructorul copy, care va apela īn fond copy() pentru ramura stīnga a arborelui.

. Ca si īn cazul functiei add(), nodurile sīnt duplicate cu ajutorul functiei duplicate(). Functia va avea implementarea concreta īn clasele derivate din Node.

Folosirea claselor Node si Tree

Vom exemplifica folosirea celor doua clase cu un program care numara cuvintele dintr-un fisier. Cuvintele sīnt definite ca secvente de caractere despartite de spatii. Programul va afisa care cuvinte sīnt prezente si de cīte ori. Īn continuare va fi prezentata clasa Strnode, derivata din Node, care va contine implementarea concreta a functiilor virtuale. Implementarea calcularii numarului de aparitii ale unui cuvīnt se va face cu ajutorul functiei already_stored(), care va incrementa variabila privata times.

class Strnode: public Node

{

public: // destructori constructori

~Strnode();

Strnode();

Strnode(Strnode const &other);

Strnode(char const *s);

Strnode const &operator=(Strnode const &other); // asignarea

// functii cerute de protocolul clasei Node

Node* duplicate() const;

int compare(Node const *other) const;

void process();

void already_stored();

private: // datele

char *str;

int times;

};

Strnode::~Strnode()

{ delete str; }

Strnode::Strnode()

{ str = 0; times = 0; }

Strnode::Strnode(Strnode const &other)

{

str = strdupnew(other.str);

times = other.times;

}

Strnode::Strnode(char const *s)

{

str = strdupnew(s);

times = 1;

}

Strnode const &Strnode::operator=(Strnode const &other)

{ if (this != &other)

{

delete str;

str = strdupnew(other.str);

times = other.times;

}

return (*this);

}

Node *Strnode::duplicate() const

{ return (new Strnode(*this)); }

int Strnode::compare(Node const *other) const

{

Strnode *otherp = (Strnode *) other;

if (str && otherp->str)

return (strcmp(str, otherp->str));

if (! str && ! otherp->str)

return (0);

return ((int) otherp->str - (int) str );

}

void Strnode::process()

{ if (str) printf("%4 d t %s \n", times, str); }

void Strnode::already_stored()

{ times++; }

void countfile(FILE *inf, char const *name)

{

Tree tree;

char buf [255];

while (1)

{ fscanf(inf, " %s", buf);

if (feof(inf)) break;

Strnode *word = new Strnode(buf);

tree.add(word);

delete word;

}

tree.inorder_walk();

}

int main(int argc, char **argv)

{

register int exitstatus = 0;

if (argc > 1)

for (register int i = 1; i < argc; i++)

{ FILE *inf = fopen(argv [i], "r");

if (! inf)

{ fprintf(stderr, "wordc: can't open \"%s\"\n", argv [i]);

exitstatus++;

}

else

{ countfile(inf, argv [i]);

fclose(inf); }

}

else countfile(stdin, "--stdin--");

return (exitstatus);

}

Concluzii finale

Īn acest laborator am introdus doua abordari pentru constructia unei clase de stocaj.

. Abordarea Storable/Storage defineste un prototip de stocat cu ajutorul functiei pur virtuale duplicate(). Īn timpul stocarii, aceasta functie este apelata pentru duplicarea unui obiect. Acest lucru mai impune implementarea functiei īn fiecare clasa derivata din Storable pentru a putea fi plasata īntr-un obiect Storage

. Abordarea template, prin folosirea clasei template Array, nu pune asemenea restrictii. Adica, conform definitiei unui obiect Array, pentru stocarea unor obiecte de (sa zicem) tip Person īntr-un tablou Array <Person> staff; tabloul nu trebuie modificat si nici clasa Person adaptata īn vreun fel.

Aceasta comparatie sugereaza ca abordarea template ar fi mult mai apropiata de clasa de tip container. Exista totusi si un dezavantaj: ori de cīte ori o clasa template este folosita pentru o clasa anume (Person si Vehicle, de ex.), compilatorul trebuie sa construiasca o noua clasa reala, fiecare cu numele sau propriu (ArrayPerson, ArrayVehicle). Īn felul acesta fiecare functie din clasa template va apare īn program de un numar de ori egal cu numarul noilor clase. Din contra, abordarea Storable/Storage necesita doar doua noi functii: un duplicator pentru Person si unul pentru Vehicle. Īnsusi codul pentru clasa container apare doar o data īn program. Īn concluzie:

. Cīnd un program foloseste doar o singura clasa container, abordarea template este de preferat: este mai usor de folosit si nu cere precautii speciale sau conversii pentru clasa continuta

. Cīnd un program utilizeaza mai multe instante ale unei clase container, este de preferat abordarea Storable/Storage: se previne duplicarea codului, desi se cer si adaptari speciale pentru clasa continuta.