Thin Template Idiom
• tech • 277 słów • 2 minuty czytania
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 ;)
Komentarze (11)
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.
projects.malcom.pl/vector_ptr.h
typedef Val vaue_type;
nie powinno byc value_type?
Poprawione, dzięki ;)
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<T*> z powodu prywatnego dziedziczenia.