CTTCG 09 Using Templates in Practice

包含模型 - The Inclusion Model

通常模板需要被包含在头文件中工作。

注意:函数模板完全特化之后也需要 inline 才能够在头文件中使用,否则会出现不同的翻译单元有重复定义的情况。

改进一:precompiled headers

可以把 pch 理解为编译器中间工作状态的一个 dump。编译器可以在读取到某些源代码之后保存其状态,包括符号表等。如果两个文件中有公共的前缀代码(比如一个标准库的头文件就包含了数千行公共代码),编译器就可以读取预先保存的状态,从而跳过对于这些代码的处理。

很遗憾的是前缀的判定是必须完全相同,即便改变头文件的包含顺序,其中与宏相关的定义也可能结果不同。由于宏无法从模块中导出,模块将可能解决这一痛点。

改进二:explicit template instantiation

只在头文件中留模板函数的声明,而不是定义。然后在其他的源文件中提供特化的定义。

// base.hpp
template <class T>
T foo(T t);

// second.cpp
#include "./base.hpp"
template <>
int foo<int>(int t) {
    return t;
}

// main.cpp
#include "./base.hpp"
int main() {
    auto x = foo(1);
    std::cout << x << '\n';
}

这样不用所有用到函数模板的翻译单元都去编译一遍函数。缺点是只能依赖枚举提供特化。

注意:在 second.cpp 中只提供通用模板函数定义是无法链接的,因为模板函数只是生成规则,并不是具体的函数,会无法匹配。同时,只在 base.hpp 声明特化的函数而不给出定义,试图依赖 second.cpp 的规则去生成函数也是无法链接的。

// base.hpp
template <class T>
T foo(T t) { return t;}

template <>
int foo<int>(int t);

// second.cpp
#include <iostream>
#include "./base.hpp"
template <>
int foo<int>(int t) {
    std::cout << "int foo<int>(int t) is called!\n";
    return t;
}

// main.cpp
#include "./base.hpp"
int main() {
    auto x = foo(1);
    std::cout << x << '\n';
}

打印结果:

int foo<int>(int t) is called!
1

这说明如果存在函数特化的声明,则不会使用模板函数的规则去在自己的翻译单元生成这个特化的函数。同样,如果注释掉了 second.cpp 中特化函数的定义,将会链接失败。使用这种方式,既可以避免枚举覆盖面的不完善,又能够为通用的一些函数节约编译时间。

改进三:modules

略。