類別模版


至今已經直接使用過類別模版很多次了,那麼如何自訂類別模版呢?基本上,類別模版就只是函式模版的概念延伸,如同函式模版實例化後的是各個不同版本的函式,類別模版實例化後的是各個不同的類別,更具體來說,是各種不同的型態。

例如 vector<int> 是個實例化後的型態,vector<char> 是實例化後另一個型態,vector<int>vector<char> 是兩種不同的型態。

因為類別模版實例化後,會是不同的類別、不同的型態,因此定義類別模版時,在傳回型態涉及類別模版本身時,必須包含模版參數,在 :: 範圍解析時也必須包含模版參數。

來看個實例吧!在〈巢狀、區域、匿名類別〉中的 IntLinkedList,只能用於 int 的元素,可以將之定義為類別模版,適用於各個指定的型態,例如:

#include <iostream>
using namespace std;

template <typename T>
class LinkedList {
    class Node {
    public:
        Node(T value, Node *next) : value(value), next(next) {}
        T value;
        Node *next;
    };

    Node *first = nullptr;

public:
    LinkedList<T>& append(T value);
    T get(int i);
};

template <typename T>
LinkedList<T>& LinkedList<T>::append(T value) {
    Node *node = new Node(value, nullptr);
    if(first == nullptr) {
        this->first = node; 
    }
    else {
        Node *last = this->first;
        while(last->next != nullptr) {
            last = last->next;
        }
        last->next = node;
    }
    return *this;
}

template <typename T>
T LinkedList<T>::get(int i) {
    Node *last = this->first;
    int count = 0;
    while(true) {
        if(count == i) {
            return last->value;
        }
        last = last->next;
        count++;
    }
}

int main() {
    LinkedList<int> intLt;
    intLt.append(1).append(2).append(3);
    cout << intLt.get(1) << endl;

    LinkedList<char> charLt;
    charLt.append('a').append('b').append('c');
    cout << charLt.get(2) << endl;

    return 0;
}

可以留意到範例中,是如何傳回類別本身型態,以及範圍解析 :: 是怎麼指定的,對於實作於類別之中的成員函式,不用範圍解析 ::,也不用重複宣告 template 模版參數名稱。例如:

#include <iostream>
using namespace std;

template <typename T>
class LinkedList {
    class Node {
    public:
        Node(T value, Node *next) : value(value), next(next) {}
        T value;
        Node *next;
    };

    Node *first = nullptr;

public:
    LinkedList<T>& append(T value) {
        Node *node = new Node(value, nullptr);
        if(first == nullptr) {
            this->first = node; 
        }
        else {
            Node *last = this->first;
            while(last->next != nullptr) {
                last = last->next;
            }
            last->next = node;
        }
        return *this;
    }

    T get(int i) {
        Node *last = this->first;
        int count = 0;
        while(true) {
            if(count == i) {
                return last->value;
            }
            last = last->next;
            count++;
        }
    }
};

int main() {
    LinkedList<int> intLt;
    intLt.append(1).append(2).append(3);
    cout << intLt.get(1) << endl;

    LinkedList<char> charLt;
    charLt.append('a').append('b').append('c');
    cout << charLt.get(2) << endl;

    return 0;
}

如果 static 資料成員是在類別外指定,記得範圍解析時也得加上型態參數,而使用 static 成員時,必須實例化,即使實例化時指定的型態與 static 無關,也是得實例化。例如:

#include <iostream>
using namespace std;

template <typename T>
class Foo {
    static int wat;
public:
    static int wat10(); 
};

template <typename T>
int Foo<T>::wat = 10; 

template<typename T>
int Foo<T>::wat10() {
    return wat * 10;
}

int main() {
    cout << Foo<double>::wat10() << endl;
    return 0;
}

模版類別中若要宣告 friend 比較麻煩,因為 friend 與類別之間有耦合關係,因此必須事先做好宣告,宣告 friend 時才看得到彼此,例如:

#include <iostream>
using namespace std;

template <typename T>
class Foo;

template <typename T>
void foo(Foo<T> t);

template <typename T>
class Foo {
    T t;
public:
    Foo(T t) :t(t) {}
    friend void foo<T>(Foo<T> t);
};

template <typename T>
void foo(Foo<T> f) {
    cout << f.t << endl;
}

int main() {
    Foo<int> f(10);
    foo(f);

    return 0;
}

實例後的類別型態與朋友之間的型態是對應的,例如 Foo<int>void foo(Foo<int>) 才會是朋友,與 void foo(Foo<char>) 不會是朋友。