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 ;)
probowales wykorzystac zalaczony plik w jakims projekcie? spojrzalem sobie na kod zrodlowy i widac w nim sporo literowek ktorych kompilator nie przepusci …
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.
http://projects.malcom.pl/vector_ptr.h
typedef Val vaue_type;
nie powinno byc value_type?
Poprawione, dzieki ;)
wszystko z // element access powinno byc rzutowane z reinterpret_cast
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 :(
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.
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...
zmienilem juz okolo 20 vectorow
na wszystkich wykorzystywane sa iteratory
na niektorych algorytmy find
i nawet nie drgnelo :)
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;
}
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.