你可能还停留在 C 库的编译 - 链接模型的时代里,对 C++ 中模板库的机理知之甚少。
不光是 STL,C++ 中的模板,通常情况下(有极少例外,下面会讲),功能都是通过头文件提供的,不需要也绝对不可能通过动态库、静态库提供。
举个最基本的例子:
template <typename T> T generic_add(const T & a, const T & b) { return a + b; }
编译器编译到这里连 T 具体是什么类型都不知道呢,怎么给你生成二进制啊?
换句话说,只有到了用到 generic_add 的地方(可以实例化的地方),编译器才能给你生成代码:
比如知道了 T 是 x64 下的 16 位整数类型:
int16_t use(int16_t x, int16_t y) { return generic_add(x, y); }
才能在这里给你生成 addw 指令,做 16 位加法
比如知道了 T 是 x64 下的 32 位整数类型:
int32_t use(int32_t x, int32_t y) { return generic_add(x, y); }
才能在这里给你生成 addl 指令,做 32 位加法
比如知道了 T 是 x64 下的 64 位整数类型:
int64_t use(int64_t x, int64_t y) { return generic_add(x, y); }
才能在这里给你生成 addq 指令,做 64 位加法
而且我们还要考虑到 T 还有可能是其他的类型,比如各种无符号整型,可能是浮点型,甚至可能是重载了 operator+ 的类类型,有成千上万种可能。
所以作为通用的、泛型的模板,不可能在动态库、静态库中就提供好二进制——怎么可能为成千上万种类型都提前生成好二进制呢?甚至还有还没有写出来的类型,怎么可能提前生成呢?
这也是为什么模板又叫模板元编程。“元编程” 的意思就是用代码生成代码。给了 short 类型,才能生成一个确定的函数 short generic_add<short>(const short &, const short &);给了 int 类型,才能生成一个确定的函数 int generic_add<int>(const int &, const int &) 。
std::sort 也是完全相同的道理。
给了一对 int *,它才能知道,哦,要生成为 int 类型排序的代码;
给了一对 long *,它才能知道,哦,要生成为 long 类型排序的代码;
给了一对 std::deque<int>::iterator,它才能知道,哦,排序的对象是 int 类型,而且这个 int 类型不是接连放在内存上的,而是按照 deque 的编码规则,分段存在内存上的,我在读取和写入数据的时候,需要特别处理。
我们就拿 std::sort 来研究,编译之后看它生成了什么:
#include <algorithm> void sort_wrapper(int * first, int * last) { std::sort(first, last); }
g++ test.cpp -o test.o -c -O2 -std=c++11
可以看到,排序所用到的所有的汇编已经全部都在目标文件里面了:
void std::__adjust_heap<int*, long, int, __gnu_cxx::__ops::_Iter_less_iter>(int*, long, long, int, __gnu_cxx::__ops::_Iter_less_iter) [clone .isra.0]: leaq -1(%rdx), %rax pushq %rbp movq %rdx, %rbp ... 此处省略若干行 ... popq %rbx popq %rbp ret void std::__insertion_sort<int*, __gnu_cxx::__ops::_Iter_less_iter>(int*, int*, __gnu_cxx::__ops::_Iter_less_iter) [clone .isra.0]: cmpq %rsi, %rdi je .L29 pushq %r14 ... 此处省略若干行 ... popq %r12 popq %r13 popq %r14 ret .L29: ret void std::__introsort_loop<int*, long, __gnu_cxx::__ops::_Iter_less_iter>(int*, int*, long, __gnu_cxx::__ops::_Iter_less_iter) [clone .isra.0]: movq %rsi, %rax subq %rdi, %rax cmpq $64, %rax ... 此处省略若干行 ... movq %rbx, %rdi call void std::__introsort_loop<int*, long, __gnu_cxx::__ops::_Iter_less_iter>(int*, int*, long, __gnu_cxx::__ops::_Iter_less_iter) [clone .isra.0] movq %rbx, %rax subq %r12, %rax ... 此处省略若干行 ... movq %r13, %rsi call void std::__adjust_heap<int*, long, int, __gnu_cxx::__ops::_Iter_less_iter>(int*, long, long, int, __gnu_cxx::__ops::_Iter_less_iter) [clone .isra.0] ... 此处省略若干行 ... ret sort_wrapper(int*, int*): cmpq %rsi, %rdi je .L84 pushq %r12 ... 此处省略若干行 ... addq %rdx, %rdx call void std::__introsort_loop<int*, long, __gnu_cxx::__ops::_Iter_less_iter>(int*, int*, long, __gnu_cxx::__ops::_Iter_less_iter) [clone .isra.0] cmpq $64, %rbx ... 此处省略若干行 ... movq %rbx, %rsi call void std::__insertion_sort<int*, __gnu_cxx::__ops::_Iter_less_iter>(int*, int*, __gnu_cxx::__ops::_Iter_less_iter) [clone .isra.0] cmpq %rbx, %rbp ... 此处省略若干行 ... popq %r12 jmp void std::__insertion_sort<int*, __gnu_cxx::__ops::_Iter_less_iter>(int*, int*, __gnu_cxx::__ops::_Iter_less_iter) [clone .isra.0] .L84: ret .L80: movq %rbx, %rsi addq $4, %rbx movl %ecx, (%rsi) cmpq %rbx, %rbp jne .L79 jmp .L72
所以也无需链接任何库,就可以实现排序——该有的二进制,已经有了。
当然凡事也皆有个例外,不是所有跟模板牵扯上关系的东西,都一定是头文件提供的,也可能会在动态库或者静态库里:
1)
比如模板的全特化实现可以就放在动态库或静态库里。因为在这一组特别的模板参数下,所有的模板参数已经确定了,我已经能够确定了即将要生成什么样的二进制。
2)
再比如,模板所用到的上层的非模板组件就可以放在动态库或静态库里。
最典型的就是 libstdc++ 中的 list
list 虽然是模板的,可能有 list<int>,list<float> 等等,但是链表的不少操作都是模板参数无关的,比如在链上挂上一个节点、摘下一个节点,以及翻转一个链表、交换两个链表等等。于是 libstdc++ 的实现中就巧妙地抽象出了链表节点的公共基类 _List_node_base,并将这些模板参数无关的代码提取出了公共的函数。这些函数的实现就是 libstdc++ 作为动态库或者静态库提供的。目的是加快编译过程、减小模板带来的二进制膨胀的影响。
3)
再比如,可以显式实例化模板,先行一步生成目标文件;C++11 以后,可以在其他地方做 extern template 申明,不进行实例化,推迟到最后的链接阶段链接含有实例化的目标文件。这一套操作下来可以节省重复实例化浪费的时间,从而加快编译速度。但是运用的前提也是,所有模板参数已经确定。
最典型的,libstdc++ 提前实例化了 basic_istream<char>、basic_ostream<char>,也就是大家天天见的 std::cin、std::cout 的类型;一起提前实例化的还有 std::basic_string<char>,也就是 std::string,同样是天天见的。因为这些类实例太常见了,提前实例化的好处上文已述。
这个问题其实,主要取决于你的团队,以及商业模式。
我假定你一个便携小工具是没打算赚大钱的,只是顺带附送的工具,从这个角度出发的话,「不可能专门为了这个项目去培训或者招聘」。
那么结果就很显然了:你的团队成员会什么技术,那就用什么技术。什么方法能立即上手就用它。——其它的因素都是次要的。因为理论上,现在任何技术,都可以用来写桌面应用。所以技术本身不是痛点,痛点是你的团队成员懂什么,最喜欢用什么,那就赶快用上。
我很难相信为了一个便携小工具你们会专门让团队学习培训新技术或者专门招一个懂新技术的人来带队。如果是这样的话,那参考其它回答。