» »

[c++] Pomoč pri izdelavi std::vector "wrapperja"

[c++] Pomoč pri izdelavi std::vector "wrapperja"

zhigatsey ::

Živjo,

Rad bi naredil en razred, ki bi bil ubistvu "ovojnica" std::vectorja, za lažje programiranje recimo takole

template<class T>
class TarVectorWrapper {
    private:
        vector<T> FVector; 
    public:    
        TarVectorWrapper() {};      
        virtual ~TarVectorWrapper() { Clear(); };
        void Clear() { FVector.clear(); };
        void PushBack(T AItem) { FVector.push_back(AItem); };
};    


Dodal bi še metode Get(unsigned int AIndex), Delete(unsigned int AIndex) in podobno. Zaustavi pa se mi pri metodi Clear() (ali pa delete). V primeru če bi shranjeval v vector kazalce (
 TarVectorWrapper<string*> 
), bi rad da mi metoda Clear tudi uniči shranjeni objekt in ne samo pobriše kazalce. Kako naj to izpeljem, če je sploh mogoče, ali se da preverit če je shranjeni podatatek v vectorju kazelec ali navadna spremenljivka. In v primeru da je kazalec se sprehodim čez vector in uničim objekte na katere kažejo kazalci v vektorju.

Hvala za odgovore

Gundolf ::

Ne vem zakaj bi rad wrappal vector, ker ze sam vsebuje vse, kar si tu navedel.

Vse knjige tudi odsvetujejo hranjenje pointerjev v vektorju, predvsem zaradi varnosti pred memory leaki. Primer leaka:
std::vector<int> iVec;
iVec.push_back(new int(10));

Po murphyjevem zakonu ti bo usplo narediti nov int (new int(10)), potem pa bo zmanjkalo spomina ko bo metoda push_back hotela povecati velikost vectorja. Ce se to zgodi imas leak, kajti tega novonarejenega inta ne mores vec pobrisati saj je njegov naslov izgubljen.

Drugace si pa poglej nacela RAII (Resource Aquisition is Initialisation) in poskusaj cim manj uporabljati new / delete.

Ce se vseeno odlocis ubrati tvojo pot pa tole:
Namesto da imas vektor kot member novega classa, lahko novi class izpeljes iz vectorja.
V destruktorju ti ni treba klicati clear, vectorjev destructor sam poskrbi za brisanje vseh elementov.
Ce hoces uniciti objekte, za katere imas pointerje se pa enostavno zapelji cez celo tabelo in delete vsak element.
Obstajajo t.i. specializacije templateov. To pomeni, da lahko naredis specializiran template (specializiras lahko posamezne metode - le katere zelis), ki bo deloval le na pointerjih. Se pravi napises
template<class T> TarVectorWrapper<T>::Clear() {/*tu notri koda, ki bo brisala navadne elemente*/}
template<class T*> TarVectorWrapper<T*>::Clear() {/*tu notri koda, ki bo brisala pointerje*/}

Upam da sem prav napisal, drugace pa si poglej v kaksno knjigo ali na netu pod partial template specialization. Se pravi, naredis osnoven template in template, ki se bo uporabil, kadar mu bos kot tip podal pointer.

you have been warned >:D

No ok, morda ne bo slo tako zlahka. Verjetno bos moral napisati vecino kode dvakrat, eno verzijo za navadn template, eno pa za specializiran.
template<>
class TarVectorWrapper<T*> {
  /*
  tu notri pa vse se enkrat
  */
}

Zgodovina sprememb…

  • spremenil: Gundolf ()

zhigatsey ::

Hvala za izčrpen odgovor, vendar imam vseeno še nekaj vprašanj,

Vse knjige tudi odsvetujejo hranjenje pointerjev v vektorju, predvsem zaradi varnosti pred memory leaki. Primer leaka:
Nekdo mi je svetoval, naj v vektorju vedno uporabim pointerje na objekte razen za recimo double, int, long itd... ampak pustmo to...
Če prav razumem so v vektorju nanizani iteratorji, ki so v bistvu kazalci na objekte in sklepam, da je res brezveze uporabiti pointerje.
Ali se motim. Kle bi mi prav prišel kakšen nasvet (se bom pa potrudil in tudi sam prebra lkakšen Effective STL...:) )

OwcA ::

STL kontejnerjih strašiti s kazalci je povsem nepotrebno, saj dobiš večino prednosti, ki jih ti prinašajo preko iteratorjev (učinkovito sprehajanje sem ter tja po kontejnerju) in alokatorjev (urejanje s spominom).
Otroška radovednost - gonilo napredka.

Gundolf ::

Ko se govori o templateih bos pa od mene dobil vedno izcrpen odgovor:P Sej vem, character flaw8-)

Najbolje je ce si std::vector predstavljas kot navadno tabelo. Le da namesto int tabela[100] (ali pa int* tabela = new int[100]) napises
std::vector<int> tabela(100)
Uporaba vectorja je sedaj lahko popolnoma identicna uporabi obicajnega arraya. Tudi implementacija je skoraj identicna. V vectorju je pac dinamicno alocirana tabela. Zato so tudi fancy iteratorji u bistvu le navadni pointerji. A tega ne sedaj narobe razumeti. Nima vsak element tabele svojega kosa alociranega pomnilnika, ki bi ga lahko posebej zbrisal (unicil). Vsi si delijo en kos pomnilnika, v njem pa so enostavno zaporedno nametani (tako kot v int* tabela). Torej prakticno identicna koda:
// c way
int* tabela = new int[100];
for (int* iter = tabela; iter < tabela + 100; ++iter) {
   std::cout << *iter;
}
delete[]tabela;
// pa se c++ way
std::vector<int> tabela(100);
for (std::vector<int>::iterator iter = tabela.begin(); iter != tabela.end(); ++iter) {
   std::cout << *iter;
}

Sedaj pa dodatki vektorja. Vsi elementi so vedno initializirani. To v primeru intov pomeni da imajo vrednost 0, v primeru tvojih razredov pa, da se vedno klice default konstruktor. Zadevo je zelo enostavno kadarkoli povecati, (funkcije resize, push_back). In navsezadnje ob unicenju tabele (ki ga ni treba explicitno klicati) se unicijo vsi elementi (klicejo se destruktorji).

To, da so ti rekli, da je bolje dajati pointerje v vector se nanasa lahko le na pocasnost pri dinamicnem vecanju (imas tabelo z 1M elementi, dodas se enega, sistem bo naredil novo tabelo z 2M elementi, skopiral elemente v to novo tabelo, unicil elemente stare tabele, unicil staro tabelo). Ce so v tabeli le pointerji je tudi to se vedno dokaj hitro. Ce pa imas notri velike in komplexne razrede s obilnimi konstruktorji in destruktorji bo stvar pokleknila. Zato pa obstaja funkcija reserve(size), ki vectorju ze vnaprej doloci dovolj rama (ce ves priblizno koliko elementov nameravas vtakniti vanjo).

OwcA ::

Omeniti velja še, da lahko uporabiš tudi kakšen drug kontejner kjer je dodajanje elementov cenejše (na račun česa drugega seve).
Otroška radovednost - gonilo napredka.

zhigatsey ::

Samo tko, zdej sem naredil na tak način, da imam še na preverjanje napak pri možnem out of range. Če mate kakšno
pripombo....

//------------------------------------------------------------------------------
// TarVector
//------------------------------------------------------------------------------

template<class T>
class TarVector : public vector<T> {                        
    public: 
        // Default ctor
        TarVector() {};  
        virtual ~TarVector() {};  
        // GetItem        
        bool GetItem(size_t Index, T& Item)  
        {  
            try
            {
                Item = at(Index);
                return true;
            }
            catch (out_of_range &e)
            {
                cerr << "Exception: " << e.what() << ", index: " << Index << endl;
                return false;
            }
        };
        
        // Delete 
        void DeleteItem(size_t Index)
        {
            try
            {
                T* iter = begin() + Index;
                erase(iter);
            }
            catch (out_of_range &e)
            {
                cerr << "Exception: " << e.what() << ", index: " << Index << endl;
            }               
        }            
};

Gundolf ::

Ce uporabljas default stvari, jih ni treba definirati:
TarVector() {};
virtual ~TarVector() {};
Prav tako ne vem zakaj virtualen destruktor. Razen ce seveda ne mislis izpeljati drugih classov in jih potem uporabljati v kombinaciji s tem TarVectorjem. Pa se to bi najverjetneje imelo smisel le, ce bi imel se druge virtualne funkcije.

Drugace so pa to le opombe bolj kot pripombe. Ker si se cisto now. ;). To so tako ali tako le se mali tweaki, ki poskrbijo za cim bolj optimalno kodo.

Aja, ce mislis da bos veliko iskal in odstranjeval elemente izven veljavnega obmocja raje preverjaj s size() in ne dodajaj exception catchinga, ker ni misljen za ta namen. Je namrec pocasen in pa doda malo overheada tudi ce ne pride do exceptiona. Spet je seveda le optimizacija. Koda je cisto veljavna tudi tvoja. Sklepam pa, da si prej programiral v Javi?

zhigatsey ::

U hvala za komentar, moram rect da takih stvari u knjigah bolj malo piše....
Drugače pa v sluzbi programiramo v Delphiju...


Vredno ogleda ...

TemaSporočilaOglediZadnje sporočilo
TemaSporočilaOglediZadnje sporočilo
»

[C++] Ali je mogoče?

Oddelek: Programiranje
161508 (984) Ciklamen
»

[C++] Zapis vector<BOOL> v binarno datoteko

Oddelek: Programiranje
13986 (790) mn
»

[C++] prevajalnik hoce konstruktor za strukturo

Oddelek: Programiranje
182432 (2136) Tr0n
»

[C++] Functor za izračun osnovne statistike podatk. strukture (vector,list...)

Oddelek: Programiranje
91405 (1318) Vesoljc
»

kazalci in polje

Oddelek: Programiranje
51606 (1527) rasta

Več podobnih tem