Thin Template
Szablony są bardzo elastycznym elementem języka programowania, a ich wykorzystywanie jest bardzo użyteczne.
Użycie ich pozwala redukować i minimalizować pisanie oraz powielanie kodu. Jak wiemy konkretyzacja dla danego typu wykonywana jest tylko do używanych metod w szablonach klas, przez co nie jest generowany niepotrzebny kod.
Niestety generowany kod wynikowy jest już nieco rozbudowany. Każda konkretyzacja szablonu jest osobna klasa, a dla każdej klasy generowany jest pełny kod potrzebnych funkcji. W przypadku konkretyzacji wzorca typem wskaźnikowym dla każdego rodzaju wskaźnika zostanie wygenerowany „inna” klasa a wraz z nią „podobny” kod wynikowy.
Dla zredukowania generowanego kodu obiektowego, można zastosować ciekawy idiom – Thin Template.
Ogólnie idea jest prosta, wszystkie wskaźniki maja ten sam rozmiar i można je rzutować na typ void*. Zatem można zkonkretyzować dany szablon dla typu void*, a następnie opakować to w bardzo prostą szablonową klasę interfejsową, która będzie zajmowała się odpowiednim rzutowaniem z void* na typ docelowy. W ten sposób zachowamy kontrole typów, a kod wynikowy zostanie wygenerowany tylko raz, dla typu void*. I to wszystko przy zerowych kosztach dodatkowych!
Szkoda ze standardowa biblioteka nie zawiera skonkretyzowanej wersji niektórych kontenerów dla void* oraz ich specjalizacje dla dowolnych typów wskaźnikowych, dziedziczących po konkretyzacji dla void*:
template<typename T> class vector { ... }; template<> class vector<void*> { ... }; template<typename T> class vector<T*> : private vector<void*> { public: void insert (T* v) { vector<void*>::insert(v); } T* at(int index) { return static_cast<T*>(vector<void*>::at(index)); } ... }; |
W jednym z projektów będę wykorzystywał vector do przechowywania rożnych typów wskaźnikowych, więc, aby uniknąć rozrastania się kodu wynikowego napisałem prostą klasę interfejsową vector_ptr, opakowywującą specjalizację vectora dla void*.
Kod jest dostępny na projects.malcom.pl, na licencji MIT. Może komuś również okaże się pomocny ;)
gruby:
19/03/2009 @ 20:48:50 | #1
probowales wykorzystac zalaczony plik w jakims projekcie? spojrzalem sobie na kod zrodlowy i widac w nim sporo literowek ktorych kompilator nie przepusci …
malcom:
19/03/2009 @ 21:03:47 | #2
Jesli chodzi o naglowek i klase vector_ptr to jak najbardziej dziala poprawnie.
Jedynie zauwazylem literowke w kodzie zamieszczonym w notce, kod ten byl pisany z palca, jedynie jako zaprezentowanie samej idei idiomu Thin Template.
gruby:
19/03/2009 @ 21:40:54 | #3
http://projects.malcom.pl/vector_ptr.h
typedef Val vaue_type;
nie powinno byc value_type?
malcom:
19/03/2009 @ 21:55:17 | #4
Poprawione, dzieki ;)
gruby:
20/03/2009 @ 11:06:13 | #5
wszystko z // element access powinno byc rzutowane z reinterpret_cast
malcom:
20/03/2009 @ 11:18:59 | #6
Racja, static_cast sie tu nie nadaj, rzutowaniem referencji, a nie jakby sie moglo wydawac golych wskaznikow.
Dziwne, ze tego predzej nie udalo mi sie wychwycic, przy testowaniu :(
gruby:
20/03/2009 @ 11:54:29 | #7
Pod jakim kompilatorem sprawdzales efekty?
Zamienilem 10 roznych vectorow ktore trzymaly wskazniki (na 10 roznych typow) i kompilator z Visual Studio 2005 generuje exe takiej samej wielkosci niezaleznie od tego czy stosuje Twoj vector_ptr czy tez standardowy vector.
malcom:
20/03/2009 @ 12:36:52 | #8
Efekty sa niezauwazalne przy kilku wektorach.
Zalezy to od kilku czynnikow, jak wiemy szablonowy, nieuzywany kod nie jest generowany, a tym samym kompilowany, wiec trzeba nie tylko utworzyc obiekt, ale takze na nim operowac, z jakimis ciezszymi metodami, ktorych kod jest dlugi i zostanie wygenerowany dla kazdej instancji szabonu osobno, np. push_back, insert…
Przy 4 vecotrach wskaznikow, z iteracja jednego i insertami w pozostalych, GCC 3.4.1 wygenerowal kod o 2KB lzejszy (532KB < 534KB) przy zastosowaniu vector_ptr w stosunku do standardowego vectora, kompilator z VC9 kod wynikowy o 1KB lzejszy (14KB < 15KB).
Sa to male roznice, prawie nie istniejace, najwieksze efekty z stosowania idiomu mozna osiagnac przy klasach szbalonowych, gdzie dla kazdej konkretyzacji wzorca generowane jest multum kodu…
gruby:
20/03/2009 @ 13:07:36 | #9
zmienilem juz okolo 20 vectorow
na wszystkich wykorzystywane sa iteratory
na niektorych algorytmy find
i nawet nie drgnelo :)
misiek:
29/03/2009 @ 17:01:23 | #10
Specjalizacja jest niepotrzebna bo nie można użyć vector_ptr w kontekście vector, gdyż std::vector nie ma destruktora wirtualnego. W konsekwencji poniższy kod daje zachowanie niezdefiniowane:
void foo(vector *v) {
delete v;
}
int main()
{
vector_ptr *v = new vector_ptr;
foo(v);
return 0;
}
malcom:
29/03/2009 @ 17:20:28 | #11
Formalnie tak, ale w tym przypadku, biorac pod uwage ze oba vectory maja taka sama reprezentacje w pamieci, vector_tr nic nie dodaje od siebie, a dtor vector_ptr nic nie robi, wiec powinno dzialac to wedlug zamierzen.
Jedyny problem jest z niejawna konwersja vector_ptr do vector z powodu prywatnego dziedziczenia.