Programowanie w języku C/C++

Szablony klas

  • 2009-04-09 14:43
  • 5 komentarzy
  • 16362 odsłony
  • Oceń ten tekst jako pierwszy
Spis treści

          1 Wstęp
          2 Szablony
          3 class i typename
          4 Inne zastosowanie typename
          5 Nazwy typów jako argumenty szablonowe
          6 Rozdzielenie deklaracji od implementacji

Wstęp


Szablony klas, podobnie jak szablony_funkcji służą do zamknięcia wielu bardzo podobnych klas w jednej (szablony funkcji robiły to z funkcjami). Dajmy na przykład zastaw klas przechowujących parę danych:

class classIntInt
{
    public:
        int a, b;
        int dodaj()
        {
            return a+b;
        }
};
 
class classIntChar
{
    public:
        int a;
        char b;
        int dodaj()
        {
            return a+b; // niekonieczne rzutowanie, między char a int zachodzi konwersja niejawna
        }
};
 
class classCharChar
{
    public:
        char a, b;
        char dodaj()
        {
            return a+b;
        }
};

Klasy te są do siebie bardzo podobne, mają identyczne składowe i tą samą metodę dodaj(), różnią się tylko typami. Klasy są oczywiście banalne i nikt nigdy nie będzie miał (chyba) potrzeby ich robić. Wracając do tematu, nigdy nie będziemy w stanie stworzyć tych klas dla każdej party typów. Można to osiągnąć jednak za pomocą szablonów klas.

Szablony


Ogólna postać klasy szablonowej wygląda następująco:

template<argumenty_szablonowe>class nazwa_klasy
{
  // wnętrze klasy
};

Nazwa klasy i jej wnętrze pozostają niezmienne (wnętrze tylko do pewnego stopnia) w stosunku do zwykłych klas. Jednak pojawia się nowy element, argumenty szablonowe. Co może się tam znajdować przeczytacie w dalszej części artykułu.

class i typename


W szablonach można się spotkać z nowym słowem kluczowym typename, a także ze starym class, które jednak otrzymuje w tym wypadku nowe znaczenie. Są one równoznaczne i pozwalają nam niejako wprowadzić nowy typ do klasy, a właściwie przypisać jakiemuś już istniejącemu typowi nową nazwę. Ich działanie można porównać do działania słówka typedef, jednak używa się ich w zupełnie inny sposób.

Weźmy sobie przykład:

template<typename TypGlowny, typename TypPoboczny>class  klasa
{
    public:
        TypGlowny a;
        TypPoboczny b;
        TypGlowny dodaj()
        {
            return a+TypGlowny(b);
        }
};

Mamy tu dwa argumety szablonowe typu typename (można było równie dobrze użyć class). Oznacza to że utworzyliśmy typy TypGlowny i TypPoboczny, których można używać tylko wewnątrz klasy. Stworzenie teraz nowego obiektu tej klasy wyglądałoby tak:

klasa<int, char> obiekt;

Stworzyliśmy w ten sposób obiekt  typu klasa przechowujący elementy typu int i char, przy czym int jest typem głównym (na który dokonuje się rzutowanie i który jest zwracany przez funkcję dodaj()). Należy zauważyć, iż argumenty szablonowe muszą spełnić pewien warunek - musi być możliwość dokonania konwersji typu pobocznego na typ główny.

W przypadku klas szablonowych warto zdecydować się na jedno ze słów class i  typename, bądź przyjąć pewne założenia kiedy ma być które używane, aby nie robić bałaganu w kodzie. Można np. używać słówka typename kiedy klasa ma dotyczyć dowolnego typu, class zaś, kiedy muszą zostać spełnione pewne warunki między typami.

Inne zastosowanie typename


Słowo typename może być użyte nie tylko w liście parametrów, ale również wewnątrz szablonu. Służy ono wtedy jako wskazówka dla kompilatora, że podana nazwa jest typem. Poniższy kod nie skompiluje się poprawnie, ponieważ kompilator potraktuje nazwę Numer jako zmienną statyczną klasy T:
template<typename T>
class incorrect {
    T::Numer i;
    };


Aby kompilacja przebiegła poprawnie, należy dodać informację, że Numer jest typem zadeklarowanym wewnątrz klasy T:
template<typename T>
class correct {
    typename T::Numer i;
    };

Po tej modyfikacji szablon kompiluje się poprawnie, i może zostać wykorzystany:
struct Test { typedef int Numer; };
correct<Test> x;


Nazwy typów jako argumenty szablonowe


Jako argumenty szablonowe można także używać zwykłych typów wbudowanych, bądź zdefiniowanych. W takim przypadku argumenty szablonowe stają się stałymi klasy. Weźmy przykład:

template<float wspolczynnik>class klasa
{
    public:
        int a, b;
        float oblicz()
        {
            return (a + b) * wspolczynnik;
        }
};

I obiekt klasy:

klasa<0.5> obiekt;
obiekt.a = 2;
obiekt.b = 4;
int wynik = obiekt.oblicz();

Nasza klasa operuje na liczbach naturalnych i posiada funkcję zwracającą liczbę zmiennoprzecinkową. Klasa posiada też stałą wspolczynnik typu float, w naszym obiekcie równą 0.5. Jak łatwo wyliczyć zmienna wynik będzie wynosiła 3 ((2+4)*0.5). Takie rozwiązanie daje nam możliwość tworzenia stałych które mają wartość nadawaną przez programistę z zewnątrz (oczywiście można użyć do tego konstruktora). Można także łączyć oba typy argumentów szablonowych.

Rozdzielenie deklaracji od implementacji


Oddzielenie deklaracji od implementacji klasy szablonowej ma się następująco:

template<class typ>class klasa
{
    private:
        typ a;
    public:
        typ foo();
};
 
template<class typ>typ klasa<typ>::foo()
{
  return a;
}

Fragment kodu template<class typ="typ"> definiuje nam nowy typ i pokazuje, że mamy do czynienia z szablonem, typ to typ zwracany przez funkcję, klasa<typ> oznacza, że jest to metoda klasy klasa<typ> (czyli np. klasa<int>, klasa<char>), :: to operator zakresu, foo() to nazwa funkcji.

Można oczywiście rozdzielić szablon na dwa pliki plik.h i plik.cpp.

Zobacz też:

5 komentarzy

traktor90 2012-04-05 19:48

@Intelli
Taki kompilator to np. EDG. Wykorzystuje model separacji do tego. Przy tworzeniu takiego szablonu należy oprzedzić jego deklarację słowem export. Visual 2010 tego nie wspiera. Jest to rzecz bardzo trudna do implementacji.(dla twórcy kompilatora)
Visual Studio wyrzuca takie ostrzeżenie:
warning C4237: 'export' keyword is not yet supported, but reserved for future use

Intelli 2009-04-14 17:33

"Można oczywiście rozdzielić szablon na dwa pliki plik.h i plik.cpp"
Czy autor zna kompilator, który to wspiera? Bo ja osobiście nie spotkałem się z takim cackiem.

manfredek 2009-04-09 14:48

Ehe... <quote=standard, 14.1.4>A non-type template-parameter shall have one of the following (optionally cv-qualified) types:
— integral or enumeration type,
— pointer to object or pointer to function,
— reference to object or reference to function,
— pointer to member.</quote>A template<float wspolczynnik> raczej się do tego nie stosuje.

DzieX 2006-12-26 01:55

Z tym typename to nie do końca prawda, że jest wymienialne z class. Spotkałem się z tym, że klasa::iterator, podawane jako parametr "class" wywalał kompilator (gcc). Natomiast typename działał poprawnie. Trzeba by ze standardu doczytać jaka jest różnica.

Nitro_67 2006-09-18 20:36

Bardzi przydał mi się ten artykuł. Dzięki wielkie.