编译器并不会为一个单纯的模板生成代码,而只有实例化一个模板才会生成实际的类,而不同的实例化之间毫无关系。既然如此函数的定义与实现自然不能够分成.h和.cpp文件来使用(隐式实例化)
当然,C++是自由的,仍然有方法能够实现分文件,即显示实例化,即在定义模板之后便对其进行指定类型的实例化,供给其他文件调用。这个特点也可以用在静态库的导出中,模板本身是没有办法像普通函数被导入静态库的二进制代码中的,需要首先进行显式实例化
与静态库相对的是动态库的导出,在静态库中,连接器仅仅是简单的进行代码的链接,而在动态库中需要有符号导出表,运行时的动态链接,二进制接口(ABI)。因此有以下几种情况
仅在头文件中定义,动态库的作者和使用者都#include 代码在主程序直接实例化,动态库的二进制文件没有这段代码,而代码逻辑被拷贝到每一个调用他的exe/so中。
进行了显示实例化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #ifdef EXPORT_DLL #define DLL_API __declspec(dllexport) #else #define DLL_API __declspec(dllimport) #endif template <typename T>class DLL_API MyBuffer { public : void push (T data) ; }; template class DLL_API MyBuffer<int >; template class DLL_API MyBuffer<float >;
函数模板 对于模板的初始化,可以使用typename,class,变量名可以使用T或者其他任何
1 2 3 4 5 >基本格式 >template < 形参列表 > >类声明 template <typename T>template <class Ty >
模板的常规类型推导需要注意的就是避免T的二义性,可以通过直接指定T的类型来避免,
1 2 3 4 5 6 7 template <typename T>T max (const T& a, const T& b) { return a > b ? a : b; } max <double >(1 ,1.2 )
除了以上简单的类型推导,模板中还有非常有趣的万能引用和折叠,万能引用即使用T&& 来接收参数,通过折叠能够保持参数的左值右值属性不变,进而实现完美转发 以下为折叠规则,可以看到在万能引用(&&)接收参数时可以保证左右值属性不改变
T& + & → T& T& + && → T& T&& + & → T& T&& + && → T&& 函数模板中比较重要的我认为就是可变参数模板,对于写宏函数的时候实现一些全局可用特性非常关键,具体实现如下。
1 2 3 4 5 6 template <typename ...Args>void sum (Args...args) {f (args...)f (&args...)}
类模板 类模板目前我用的还是比较少的,一个基本的定义是这样的
1 2 3 4 5 6 7 8 9 10 template <typename T>class Test { Test (T v) :t{ v } {} private : T t; }; Test t (1 ) ; Test (int ) -> Test<std::size_t >;Test t (1 ) ;
以音视频处理来举一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 template <typename T>class RingBuffer {private : T* buffer; int head; int tail; int size; public : RingBuffer (int s) : size (s), head (0 ), tail (0 ) { buffer = new T[size]; } void push (T value) { } T pop () { } ~RingBuffer () { delete [] buffer; } }; RingBuffer<float > audioBuffer (1024 ) ; RingBuffer<AVPacket*> packetQueue (50 ) ;
类模板中的函数模板
1 2 3 4 5 6 7 8 9 10 template <typename T>class Class_Template { void F (T) {} } class Test { template <typename ...Args> void f (Args&&...args) {}};
类模板中的变量模板
1 2 3 4 5 6 7 8 class Class_template { template <typename T> static const T min; } template <typename T>const T Class_template::min = {};
变量模板 需要注意,变量模板的实例化是一个全局变量 基本的定义方式如下
1 2 3 4 template <typename T>constexpr T v{};v<int >;
模板全特化 模板全特化形式上很像模板情况下的函数重载,但是实际上是完全不同的,即著名的 C++ 专家 Herb Sutter 曾说过:”Specializations don’t overload.”(特化不参与重载解析)。模板全特化是模板的附属品,编译器选定一个基础模板后才会去检查有没有对应的特化版本,且形参列表必须完全匹配,不能够存在隐式转换。而对于函数重载,每个函数都是一个独立的竞争队形,编译器会在所有的重载函数中选择一个最合适的,其中可以进行隐式转换。
1 2 3 4 5 6 7 8 9 template <typename T,typename T2>auto f (const T& a, const T2& b) { return a + b; } template <>auto f (const double & a, const int & b) { return a - b; }
而对于类的全特化
1 2 3 4 5 6 7 8 9 10 11 12 13 template <typename T>class Matrix { T* data; }; template <>class Matrix <bool > { uint8_t * packedData; };
模板偏特化 相较于全特化要求类型完全相等于A然后取得A(类、变量、函数),偏特化在使用的时候不要求template<>,e而是template ,这意味着,这个(类、变量)仍是一个模板。另外需要注意的是,仅仅有变量和类具备偏特化特性,而函数没有,原因是函数可以通过重载来实现着一个特性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 template <typename T, typename Strategy>class BufferProcessor { }; template <typename T>class BufferProcessor <T, FastDMA> { }; template <>class BufferProcessor <AVFrame, FastDMA> { };
折叠表达式 假设我们有一个参数包 args,包含元素 $E_1, E_2, \dots, E_n$,运算符为 $\circ$(比如 +),初始值为 $I$ 一元左折叠,(… op args),(((E1 + E2) + E3) … + En),省略号在左,从左往右算 一元右折叠,(args op …),(E1 + (E2 … + (En-1 + En))),省略号在右,从右往左算 二元左折叠,(init op … op args),((((I + E1) + E2) … + En),有初始值,从左侧开始“吞噬” 二元右折叠,(args op … op init),(E1 + (E2 … + (En + I))),有初始值,从右侧开始“合并”
因此通过定义不同的操作符和不同的折叠顺序,能够执行可变参数的运算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 template <typename ... T>void initComponents (T... args) { (... , args.init ()); } template <typename ... Args>void printAll (Args... args) { (std::cout << ... << args) << std::endl; }
待决名
在模板(包括别名模版)的声明或定义中,不是当前实例化的成员且取决于某个模板形参的名字不会被认为是>类型,除非使用关键词 typename 或它已经被设立为类型名(例如用 typedef 声明或通过用作基类名)。
所谓“待决”(Dependent),指的是一个名字依赖于模板参数 T。在模板被实例化之前,编译器根本不知道这个名字到底代表什么。
1 2 3 4 template <typename T>void function () { T::iterator * ptr; }
因此引入两个关键字来告知编译器这些未被实例化的、无法识别的属性
typename 用于反应类内部的类型,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 template <typename T>class PacketQueue {public : using ValueType = T; using Iterator = T*; struct A {} void push (ValueType val) { } }; template <typename T>void handle (T& obj) { typename T::Config myConfig; }
template 用于反应类内部定义的模板,例如函数模板、变量模板、类模板等等
1 2 3 4 5 6 7 8 9 10 11 struct RegularEncoder { template <typename T> void encode (T data) { } }; template <typename TEncoder>void startStreaming (TEncoder& enc) { enc.template encode <AVPakcet>(packet); }