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 ;)

11 przemyśleń nt. „Thin Template”

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

  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.

  3. 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 :(

  4. 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.

  5. 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...

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

  7. 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;
    }

  8. 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.

Odpowiedz na „grubyAnuluj pisanie odpowiedzi

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *