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)

gruby avatar
gruby
20090319-204850-gruby

probowales wykorzystac zalaczony plik w jakims projekcie? spojrzalem sobie na kod zrodlowy i widac w nim sporo literowek ktorych kompilator nie przepusci …

Malcom avatar
Malcom
20090319-210347-malcom

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.

Malcom avatar
Malcom
20090319-215517-malcom

Poprawione, dzięki ;)

gruby avatar
gruby
20090320-110613-gruby

wszystko z // element access powinno byc rzutowane z reinterpret_cast

Malcom avatar
Malcom
20090320-111859-malcom

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 avatar
gruby
20090320-115429-gruby

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 avatar
Malcom
20090320-123652-malcom

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 avatar
gruby
20090320-130736-gruby

zmienilem juz okolo 20 vectorow na wszystkich wykorzystywane sa iteratory na niektorych algorytmy find i nawet nie drgnelo :)

misiek avatar
misiek
20090329-170123-misiek

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 avatar
Malcom
20090329-172028-malcom

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.

Dodaj komentarz

/dozwolony markdown/

/nie zostanie opublikowany/