泛型函数与 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 描述共享要求 →