Tablice klonów to jedna z ważniejszych klas w ROOT. Generalnym ich zamierzeniem było napisanie tablicy która będzie szybka.
Ale o co chodzi z szybkością tablicy? Otóż rozważmy sytuację gdy potrzebujemy analizować cząstki z 1 miliona zderzeń. W jednym zderzeniu mamy ich od 500 do 1500 (średnio 1000), w najprostszej wersji programu po prostu ze zderzenia na zderzenie tworzymy nową i usuwamy starą tablicę z cząstkami. Tu jednak pojawia się problem w postaci tego, że operacja alokacji/dealokacji pamięci trwa relatywnie długo a my musimy wykonać około 1 miliona takich operacji!.
Tak więc wpadamy na inny pomysł - alokujemy rozmiar na zapas - ponieważ wiemy, że maksymalna liczba cząstek to 1500 od razu alokujemy tablicę o rozmiarze 1500 + deklarujemy zmienną która nam powie ile naprawdę jest cząstek w takiej tablicy. W ten sposób zamiast 1 miliona alokacji mamy ich jedynie 1500 (+1). I w zasadzie tym są TClonesArray - takim sprytnym zarządzaniem pamięcią opakowanym w ładną klasę, rozważmy następujący przykład:
class Czastka: public TObject{ public: Czastka(){ cout<<"operator new "<<endl; } virtual ~Czastka(){ cout<<"destruktor "<<endl; } ClassDef(Czastka,1); }; void allocate(){ TClonesArray *arr= new TClonesArray("Czastka",2); cout<<"#Event 1 start "<<arr>GetEntries()<<endl; for(int i=0;i<3;i++) Czastka *m = (Czastka*)arr->ConstructedAt(i); cout<<"#Event 1 koniec "<<arr->GetEntries()<<endl; arr->RemoveAt(2); arr->Clear(); cout<<"#Event 2 start "<<arr->GetEntries()<<endl; for(int i=0;i<4;i++) Czastka *m = (Czastka*)arr->ConstructedAt(i); cout<<"#Event 2 koniec "<<arr->GetEntries()<<endl; arr->Clear(); cout<<"#Event 3 start "<<arr->GetEntries()<<endl; for(int i=0;i<2;i++) Czastka *m = (Czastka*)arr->ConstructedAt(i); cout<<"#Event 3 koniec "<<arr->GetEntries()<<endl; delete arr; }
Wyjście takiego programu wyglądać będzie następująco:
#Event 1 start 0
operator new
operator new
operator new
#Event 1 koniec 3
#Event 2 start 0
operator new
#Event 2 koniec 4
#Event 3 start 0
#Event 3 koniec 2
destruktor
destruktor
destruktor
destruktor
Jak widać TClonesArray zachowuje się podobnie jak nasza tablica z tym, że tutaj nawet nie musimy znać maksymalnego rozmiaru zderzenia - za każdym razem gdy robimy TClonesArray:ConstructedAt klasa sprawdza czy jej aktualny rozmiar jest odpowiedni - jeśli nie, jest ona powiększana - co więcej powiększanie jest zrobione dość sprytnie - nie jest tworzona nowa tablica (co wymagałoby wielu alokacji i przepisania starej tablicy) a jedynie nowy element. Co więcej typ klasy określamy w konstruktorze TClonesArray więc zasadniczo można zmieniać typu przechowywanych w niej obiektów bez konieczności ponownej kompilacji kodu - wystarczy, że nazwa klasy będzie podawana do programu przez użytkownika albo wczytywana z pliku.
Lista najważniejszych metody klasy TClonesArray:
Metoda | Argument | Funkcja |
UncheckedAt | int | zwraca element tablicy bez sprawdzania czy on istnieje (trzeba być pewnym że tablica zawiera element, gdyż można dostać pusty wskaźnik), jest to metoda która powinna być użyta np. gdy analizujemy dane z drzewa, wtedy przy pomocy GetEntriesFast otrzymujemy informację o rozmiarze tablicy, w takim przypadku jesteśmy pewni, że dopóki nie przekroczymy tej wartości metoda ta zwróci istniejący obiekt |
ConstructedAt | int | zwraca element tablicy po upewnieniu się że on istnieje (jeśli go nie ma zostanie zaalokowany), jest to metoda która powinna być używana w celu zapisu do TClonesArray, tzn. gdzie nie jesteśmy pewni jaki jest jej rozmiar |
Clear | czyści tablicę, ale nie zwalnia pamięci, oznacza to, że UncheckedAt zwróci nam pusty wskaźnik, ale ConstructedAt nie będzie musiał od nowa alokować pamięci (o ile nie wykraczamy poza rozmiar tablicy przed Clear()) | |
Delete | czyści tablicę i zwraca pamięć zarezerwowaną dla obiektów | |
RemoveAt | int | usuwa element z tablicy (również "fizycznie") |
GetEntries | zwraca liczbę elementów w tablicy (przelicza rzeczywistą dostępną liczbę elementów w tablicy) | |
GetEntriesFast | zwraca liczbę elementów w tablicy (bazuje na indeksie ostatniego stworzonego obiektu np. przy pomocy ExpandCreateFast albo ConstructedAt) | |
ExpandCreateFast | int | zwiększa maksymalną liczbę elementów tablicy (nowe elementy są dostępne przez UncheckedAt) |
GetSize | zwraca rozmiar kolekcji (nie mylić z liczbą dostępnych elementów!) jest to rozmiar zarezerwowanej pamięci |
Należy zwrócić uwagę, że mimo iż w konstruktorze TClonesArray podajemy rozmiar alokowanej tablicy, to nie od razu możemy mieć dostęp do zaalokowanych elementów (tzn. UncheckedAt zwróci zero zaraz po takim konstruktorze). Kolejna ważna informacja - do TClonesArray można wrzucać jedynie obiekty tej samej klasy, klasa ta musi dziedziczyć bo TObject. W celu przechowywania różnych obiektów różnych klas należy użyć TObjArray.