środa, 4 marca 2009

Tablica, vector, chleb powszedni.


Hej ludzie, co słychać? Jak tam wasze zajebiste projekty, moje chwilowo stoją w miejscu, powód?, Civilization 3, fuck, nie pamietam ile razy już łamałem, niszczylem, sprzedawałem płyty z tą grą, jest zabójczo uzależniająca, wiedziałem to a mimo to kupiłem ją znowu, masakra.

Dziś na tapecie mamy tablice z języka C i pytanie o sens ich istnienia w obliczu wektora i potęgi szablonów z C++. Pytanie to mnie naszło gdy natknąłem sie na klase carray z księgi "C++ biblioteka standardowa", jest to klasa osłonowa na zwykłą tablice posiadająca interfejs STL-owy, którą wam pokaże za moment. 

Na razie zwykła tablica i jej zastosowania. Oczywiście nie znamy rozmiaru tablicy w momencie jej deklaracji stąd musimy użyć wskaaźnika na typ tablicowy. Ważne jest dla nas, żeby w dowolnym momencie utworzyć tablice o dowolnym rozmiarze i pozbyć się jej gdy nam przyjdzie ochota. Jedziemy szablonem ponieważ nie wiemy jakiego typy wierzchołki będa nam potrzebne a w każdej chwili może nam przyjść do głowy zmiana koncepcji, kod powinien być gotowy na zmiany. Wszystko gra i bucy, jedyny minus to zmienna numVerts, w której musimy trzymać rozmiar, nie idzie tego inaczej rozwiązać sizeof(verts) zwróci rozmiar pointera, sizeof(*verts) rozmiar elementu, nie mam pojęcia czy da sie to jakoś zrzutować na tablice, raczej nie. Drugi minus to brak STL-wego interfejsu, stąd nie użyjemy np: algorytmów std::copy czy std::sort. Duża zaleta tablicy to ciągły obszar pamieci jaki zajmuje, dlatego możemy pojechać z funkcjami takimi jak: 

memcpy - kopiowanie bloku pamięci 
memset - wypełnianie pamięci wartością zdaje sie int lub unsigned int

Dzięki ciągłemu blokowi pamięci unikamy też fragmentacji pamięci, toteż tablica idelanie nadaje sie do przechowywania danych cząstkach systemu cząstkowego, których jest zwykle bardzo wiele, ale o tym kiedy indziej.



template
class Geometry
{
protected:
VERTEX* verts;
long    numVerts;
void CreateArrays(long _numVerts)
{
   Clear();
   numVerts = _numVerts;
   verts    = new VERTEX[_numVerts];
}
void Clear(){ SAFE_DELETE_ARRAY(verts); }
public:
Geometry(long _numVerts)
{
   verts = 0;
   CreateArrays(_numVerts);
}
~Geometry(){ Clear();}

VERTEX* GetVerts(){ return verts; }
long    GetNumVerts(){ return numVerts; }
virtual void Draw()
{
   printf("verts size = %d\n", numVerts);
     //glVertexPointer(numVerts, GL_FLOAT, 0, verts);
   //glDrawArrays(GL_POINTS,0, numVerts);
}
}
;


Druga klasa, którą chce pokazać to Carray z książki do STL, jako wrapper na zwykłą tablice. Jej zaletą jest to że posiada interfejs typowy dla innych kontenerów, stąd cały arsenał gotowych algorytmów stoi przed nami otworem, praktyka pokazuje jednak, że nie będziemy z tego w ogóle korzystać, ponieważ tablic danych, nie wskaźników używamy tylko po to aby przekazać, skopiować, wyzerować dane. Przy czym od nadmiaru dobrego głowa nie boli, więc mogę ją dodać do swojego commona.dll. Bezczelnie nazwałem to cudo MyArray ponieważ dodałem kilka poprawek, bewzględnie należy zapomnieć o podawaniu rozmiaru jako parametru szablonu, bo wtedy naszą tablicę możemy o kant dupy rozbić. Musi być elastyczna. Zgodnie z STL-ową manierą nazwy metod są z małej literki, niech im będzie, zresztą nazwy muszą sie zgadzać aby zachować interfejs kontenerów.



template
class MyArray
{
protected:
T*          v;
std::size_t sizeV;
public:
typedef T*          iterator;
typedef const T*    const_iterator;
typedef T&          reference;
typedef const T&    const_reference;
typedef std::size_t size_type;

MyArray(std::size_t _size)
{
   sizeV = _size;
   v     = new T[sizeV];
}
void                 clear(){ SAFE_DELETE_ARRAY(v); }
iterator             begin(){ return v; }
const_iterator       begin() const { return v; }
iterator             end() { return v+sizeV; }
const_iterator       end() const { return v+sizeV; }
reference            operator[](std::size_t i){ return v[i]; }
const_reference      operator[](std::size_t i) const { return v[i]; }
reference            at(std::size_t elem){ return v[elem]; }
const_reference      at(std::size_t elem) const { return v[elem]; }
size_type            size(){ return sizeV; }
size_type            max_size(){ return sizeV; }
T*                   as_array(){ return v; }
};

//przykład MyArray
MyGeometry* geom3  = new MyGeometry(5);
Vertex3*    verts3 = geom3->GetVerts();
MyArray     arr    = geom3->GetVertObject();
arr[0].x = 9;
geom3->Draw();



Ostatni element dzisiejszego wywodu to sławetny std::vector. Jest znany pod każdą szerokoscią geograficzną, nawet na Madaskarze go używają. Ma jedną podstawową przewage nad tablicą, umie sie rozszerzać, jego problem jest niestety taki że rośnie razy dwa, gdy mu braknie miejsca. Na szczęście możemy mu zarezerwować określone miejsce z góry. Możemy go używać zamiennie z tablicą i przekazywać &vec[0] wszędzie tam gdzie jej oczekują. Myśle że może być z powodzeniem wykorzystywany tam gdzie chcemy mieć dynamiczny bufor wierzchołków, ze wskazaniem na rozszerzanie bufora, bo niestety nasz cudowny std::vector nie umie zmiejszać swojego rozmiaru. tzn zmniejszać zapotrzebowania na zużywaną pamięć. Ponieważ jest to ciągły blok, więc możemy łatwo i szybko kopiować cały vector lub jego część przez memcpy().
Ale o wiele wygodniej jest używać algorytmów STL.



//kopiowanie vector
std::vector vec1;
std::vector vec2;
std::vector vec3;
//rezerwuj pamięć na 4 elementy
vec1.reserve(4); vec2.reserve(4);
vec1.push_back(1);
vec1.push_back(2);
vec1.push_back(3);
vec1.push_back(4);
vec2.push_back(5);
vec2.push_back(6);
vec2.push_back(7);
vec2.push_back(8);
//kopiowanie vec1 do vec3
vec3.insert(vec3.end(), vec1.begin(), vec1.end());
//kopiowanie vec1 do vec2
std::copy(vec1.begin(), vec1.end(), vec2.begin());

//tablica a vector
int* tab = NULL;
tab = &vec2[0];
memset(&vec2[0], 0, vec2.size()*sizeof(int));
memcpy(&vec3[0], &vec2[0], vec2.size()*sizeof(int));
std::copy(vec1.begin(), vec1.end(), vec2.begin());

//iteratory
//iteratory powinny być szybsze, gdyż są to poitery na obiekty, nie ma kopiowania całego obiektu
std::vector::iterator it = vec3.begin();
for(; it != vec3.end(); ++it)
   printf("value = %d ", *it);

// wyciągaj obliczenie rozmiaru przed pętle
int vecSize = vec3.size();
//++i jest szybszy od i++, brak obiektu tymczasowego
for(int i = 0; i < vecSize; ++i)
    printf("value: %d",vec3[i]);


                                                                                   
W książce do pisania gier piszą żeby używać std::vector wszędzie tam gdzie można użyć tablicy. Na pewno tak, najważniejsze, że możemy go używać zamiennie z tablicą, dzięki czemu przekażemy nasz wektor jako tablice do np: jakiejś funkcji OpenGL. Ważne jest żeby prawidłowo zalokować od razu całą pamięć i nie zrobić później głupiego push_backa, bo nam rozwali pamięć. Dlatego też wacham się trochę, ze wskazaniem na wektor. Ważny jest szablon na typ, dzięki temu mamy duże pole manewru, ale ile z tego powodu może być problemów to jeszcze kiedyś napisze.

Brak komentarzy: