Thin templates

29 grudnia 2008

Jednym z najbardziej oczywistych zastosowań szablonów jest implementacja różnego rodzaju struktur danych. Zostało to wykorzystane na szeroką skalę w bibliotekach takich, jak chociażby STL. Z szablonami wiążą się jednak pewne problemy, które nie zawsze ujawniają się na architekturze x86, ale są dużą przeszkodą w przypadku systemów wbudowanych. Chodzi tutaj głównie o nadmierny wzrost objętości plików binarnych. Jednym ze sposobów na poradzenie sobie z tym jest zastosowanie idiomu thin template.

Uzasadnienie

Niżej przedstawiona klasa stack implementuje prostą kolejkę LIFO. Interfejs tej klasy wykorzystuje szablony w celu zapewnienia bezpieczeństwa typologicznego. Ewentualne prywatne składowe klasy zostały pominięte, ponieważ nie są istotne.

template<typename T>
class stack {
public:
	void push(T &);
	T &pop();
};

Obiekty tej klasy można wykorzystywać na wiele różnych sposobów w innych fragmentach kodu. Istotny jest jednak fakt, że dla każdego typu dla którego szablon jest konkretyzowany, kompilator wygeneruje osobny kod metod tej klasy. Może to doprowadzić do znacznego wzrostu rozmiaru kodu i w rezultacie zmniejszenia wydajności, a w przypadku systemów wbudowanych, może to stanowić poważną przeszkodę we wdrażaniu takiej aplikacji.

Szablony są kłopotliwe także w sytuacji, gdy biblioteka, która wykorzystuje ten mechanizm nie ma mieć ogólnodostępnego kodu źródłowego. Co prawda tutaj pewnym udogodnieniem jest słowo kluczowe export, ale z tych bardziej znanych kompilatorów tylko jeden je obsługuje. Nie jest to więc wystarczająco dobry sposób na zamknięcie kodu biblioteki.

Przy dużych projektach szablony mogą także skutecznie wydłużyć czas kompilacji programu. Innym problemem są także bardzo nieczytelne komunikaty o błędach generowane przez większość kompilatorów w sytuacji gdy pojawi się jakiś problem w samej implementacji klasy (w tym przypadku stack) lub kodu klienckiego.

Rozwiązanie

Rozwiązaniem powyższych problemów jest właśnie idiom języka C++ zwany thin template. Został on spopularyzowany w systemie Symbian. Mechanizm polega na przeniesieniu implementacji do innej, nieszablonowej klasy. Przykładowa klasa stack z poprzedniego listingu, po zastosowaniu thin template może wyglądać tak:

class stack_base {
public:
	void push(void *);
	void *pop();
}
 
template<typename T>
class stack : private stack_base {
public:
	inline void push(T &x) {
		stack_base::push((void*)&x);
	}
	inline T &pop() {
		return *(T*)stack_base::pop();
	}
};

Klasa stack_base zawiera całą implementację struktury danych i jako nieszablonowa może być umieszczona w osobnym pliku binarnym, co rozwiązuje problem zamkniętego kodu oraz długiego czasu kompilacji. Niezależnie od ilości konkretyzacji klasy stack dla różnych typów, wykorzystywana jest zawsze kod metod klasy stack_base, więc rozmiar pliku wynikowego powinien być znacznie mniejszy. Jedynymi zadaniami klasy szablonowej jest wykonanie odpowiednich rzutowań oraz przechowywanie informacji o typie.

W powyższym przykładzie metody stack::push i stack::pop są rozwijane w miejscu wykonania. Pozwala to w pewnych przypadkach na ograniczenie rozmiaru kodu, o ile same rzutowania się stosunkowo proste. Jest to spowodowane faktem, że w kodzie klienckim bezpośrednio wstawiane są wywołania metod klasy stack_base, a nieskomplikowane rzutowania nie wymagają generacji dodatkowego kodu.

Oczywiście korzystanie z metod rozwijanych w miejscu wywołania jest opcjonalne i w przypadku gdy metody klasy stack będą zmuszone wykonywać dodatkowe operacje, lub skomplikowane rzutowania przed wywołaniem metod klasy bazowej, może się okazać, że lepszym rozwiązaniem będzie pominięcie tego słowa kluczowego.

Podsumowanie

Thin templates jest niewątpliwie bardzo praktycznym mechanizmem, który pozwala znacząco zmniejszyć objętość kodu. W znakomitej większości sytuacji nie posiada, żadnych znaczących wad, poza tym, że zmusza do napisania implementacji danej klasy bez korzystania z dobrodziejstw statycznej kontroli typów. Thin templates mimo że tak często prezentowane na przykładzie struktur danych, mogą być wykorzystane w wielu innych sytuacjach, gdzie używa się szablonów, głównie do zapewnienia odpowiedniego bezpieczeństwa. W każdym razie, warto wiedzieć o takim mechanizmie, który pomaga rozwiązać część problemów związanych z szablonami w C++.

Komentarze do wpisu "Thin templates" zablokowane.

1. mina86 napisał(a):
30 grudnia 2008, 11:59:39

"Swoją rolę odgrywa tutaj także słowo kluczowe inline. Zmniejsza ono fizyczny rozmiar metod stack::push i stack::pop"

Eee... Po pierwsze (zgodnie ze standardem) w tym konkretnym przypadku nie ma żadnego znaczenia, czy to słówko jest czy go nie ma -- jeżeli metoda jest definiowana wewnątrz definicji klasy domyślnie jest ona inline.

Po drugie, wcale nie zmniejsza ono fizycznego rozmiaru metod, tylko dodaje *podpowiedź* dla kompilatora, żeby starał się tej metody w ogóle nie generować, a całe jej ciało wstawiać w miejscach wywołania.

2. Paweł Dziepak napisał(a):
30 grudnia 2008, 12:17:44

Co do pierwszego, to rzeczywiście tak mówi 7.1.2/3 i przez to pojawiła się nieścisłość we wpisie.

Jeżeli chodzi o drugie, to właśnie wstawianie ciała w miejscach wywołania pozwala na zmniejszenie rozmiaru *kodu* metody w pewnych specyficznych sytuacjach (takich jak przedstawiona w moim przykładzie). Kompilator nie musi generować instrukcji niezbędnych do rozpoczęcia i zakończenia wykonywania procedury. Wielokrotne wstawianie tej funkcji nie powoduje to wzrostu rozmiaru pliku wynikowego, ponieważ rzutowanie nie jest przekładane na żadną instrukcję assemblera. Jedynie dereferencja jest dodatkową instrukcją, ale jak to zostało wspomniane, skuteczność inline (jeżeli chodzi o to zagadnienie) ściśle zależy od rodzaju kodu.

W każdym razie, dziękuję za sugestie. Postaram się odpowiednio uściślić odpowiednie fragmenty notki.