泛型函式與 class
繁中(台灣)

泛型函式與 class

第 10 章從一個最基本的觀察開始:有時我們希望同一種程式形狀能適用於不止 一種具體型別。

在今天的 scpp 裡,起點就是熟悉的 C++ template 語法。一個泛型定義會寫出一個或多 個型別參數,而每一次具體使用都會用真正的型別去實例化它。

下面每個可執行範例都請存成 generics.scpp,然後這樣建置並執行:

scpp generics.scpp -o generics
./generics

一個泛型函式可以同時抽象多個不同的具體參數型別

函式模板可以把一種操作寫一次,然後讓不同呼叫點各自選擇自己的具體型別。

import std;

template<typename T, typename U>
T first(T left, U right) {
    return left;
}

int main() {
    std::println("{} {}", first(42, true), first('S', 9));
    return 0;
}

輸出:

42 S

這兩個呼叫並不需要先統一成某一個共同參數型別。第一次呼叫裡,T = intU = bool;第二次呼叫裡,T = charU = int。一個泛型函式頭就覆蓋了這兩種具 體情況。

一個泛型 class 可以存放實例化時指定的具體型別值

泛型 class 使用同樣的 template<typename ...> 標頭,然後在欄位和方法裡直接使用這 些型別參數。

import std;

template<typename T>
class Holder {
public:
    virtual ~Holder() { return; }

private:
    T item_;

public:
    Holder(const T& item) : item_{item} {
        return;
    }

    T get() const {
        return this->item_;
    }
};

int main() {
    Holder<int> number{42};
    Holder<char> initial{'G'};
    std::println("{} {}", number.get(), initial.get());
    return 0;
}

輸出:

42 G

Holder<int>Holder<char> 來自同一個 class 定義,但在最終程式裡,它們仍然是 兩個不同的、已經實例化好的具體型別。

每個實例化都可以分別解析依賴型別參數的資料版面配置

泛型 class 並不只是「一個在執行時抹掉型別資訊的通用盒子」。不同實例化可以產生 不同的具體儲存版面配置。

import std;

template<typename T>
class Buffer {
public:
    virtual ~Buffer() { return; }

    char storage_[sizeof(T)]{};

    int size() const {
        return static_cast<int>(sizeof(this->storage_));
    }
};

struct OneByte {
    char value;
};

struct EightBytes {
    int left;
    int right;
};

int main() {
    Buffer<int> ints{};
    Buffer<OneByte> one{};
    Buffer<EightBytes> pair{};
    std::println("{} {} {}", ints.size(), one.size(), pair.size());
    return 0;
}

輸出:

4 1 8

所以 Buffer<int>Buffer<OneByte>Buffer<EightBytes> 並不是都在共用一模一 樣的版面配置。每一個實例化都會針對自己的具體 T,分別解析出 sizeof(T)

第 10 章開頭關於泛型程式碼的工作模型

到目前為止,實用規則可以先記成這樣:

這樣一來,本章後面部分就有了共同基礎:泛型函式家族、泛型 class 家族,以及由它 們各自產生出來的具體實例化型別。


← 上一章:如何在 fail-fast、std::optional<T>std::expected<T, E> 之間做選擇 · 目錄 · 下一章:用 concept 描述共享需求 →