C++模板元编程(C++ template metaprogramming)(2)
发表于 2023-1-21 20:50:29
显示全部楼层
0 1045
|
关于模板实例化(template instantiation)(详见文献[4]模板):
- 指在编译或链接时生成函数模板或类模板的具体实例源代码,即用使用模板时的实参类型替换模板类型参数(还有非类型参数和模板型参数);
- 隐式实例化(implicit instantiation):当使用实例化的模板时自动地在当前代码单元之前插入模板的实例化代码,模板的成员函数一直到引用时才被实例化;
- 显式实例化(explicit instantiation):直接声明模板实例化,模板所有成员立即都被实例化;
- 实例化也是一种特例化,被称为实例化的特例(instantiated (or generated) specialization)。
隐式实例化时,成员只有被引用到才会进行实例化,这被称为推迟实例化(lazy instantiation),由此可能带来的问题如下面的例子(文献[6],文献[7]):
- #include <iostream>
- template<typename T>
- class aTMP {
- public:
- void f1() { std::cout << "f1()\n"; }
- void f2() { std::ccccout << "f2()\n"; } // 敲错键盘了,语义错误:没有 std::ccccout
- };
- int main(){
- aTMP<int> a;
- a.f1();
- // a.f2(); // 这句代码被注释时,aTMP<int>::f2() 不被实例化,从而上面的错误被掩盖!
- std::cin.get(); return 0;
- }
复制代码 所以模板代码写完后最好写个诸如显示实例化的测试代码,更深入一些,可以插入一些模板调用代码使得编译器及时发现错误,而不至于报出无限长的错误信息。另一个例子如下(GCC 4.8 下编译的输出信息,VS2013 编译输出了 500 多行错误信息):
- #include <iostream>
- // 计算 N 的阶乘 N!
- template<int N>
- class aTMP{
- public:
- enum { ret = N==0 ? 1 : N * aTMP<N-1>::ret }; // Lazy Instantiation,将产生无限递归!
- };
- int main(){
- std::cout << aTMP<10>::ret << '\n';
- std::cin.get(); return 0;
- }
复制代码- sh-4.2# g++ -std=c++11 -o main *.cpp
- main.cpp:7:28: error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depth= to increase the maximum) instantiating 'class aTMP<-890>'
- enum { ret = N==0 ? 1 : N * aTMP<N-1>::ret };
- ^
- main.cpp:7:28: recursively required from 'class aTMP<9>'
- main.cpp:7:28: required from 'class aTMP<10>'
- main.cpp:11:23: required from here
- main.cpp:7:28: error: incomplete type 'aTMP<-890>' used in nested name specifier
复制代码 上面的错误是因为,当编译 aTMP<N> 时,并不判断 N==0,而仅仅知道其依赖 aTMP<N-1>(lazy instantiation),从而产生无限递归,纠正方法是使用模板特例化,如下:
- #include <iostream>
- // 计算 N 的阶乘 N!
- template<int N>
- class aTMP{
- public:
- enum { ret = N * aTMP<N-1>::ret };
- };
- template<>
- class aTMP<0>{
- public:
- enum { ret = 1 };
- };
- int main(){
- std::cout << aTMP<10>::ret << '\n';
- std::cin.get(); return 0;
- }
复制代码 关于模板的编译和链接(详见文献[1] 1.3、文献[4]模板):
- 包含模板编译模式:编译器生成每个编译单元中遇到的所有的模板实例,并存放在相应的目标文件中;链接器合并等价的模板实例,生成可执行文件,要求实例化时模板定义可见,不能使用系统链接器;
- 分离模板编译模式(使用 export 关键字):不重复生成模板实例,编译器设计要求高,可以使用系统链接器;
- 包含编译模式是主流,C++11 已经弃用 export 关键字(对模板引入 extern 新用法),一般将模板的全部实现代码放在同一个头文件中并在用到模板的地方用 #include 包含头文件,以防止出现实例不一致(如下面紧接着例子);
实例化,编译链接的简单例子如下(参考了文献[1]第10页):
- // file: a.cpp
- #include <iostream>
- template<typename T>
- class MyClass { };
- template MyClass<double>::MyClass(); // 显示实例化构造函数 MyClass<double>::MyClass()
- template class MyClass<long>; // 显示实例化整个类 MyClass<long>
- template<typename T>
- void print(T const& m) { std::cout << "a.cpp: " << m << '\n'; }
- void fa() {
- print(1); // print<int>,隐式实例化
- print(0.1); // print<double>
- }
- void fb(); // fb() 在 b.cpp 中定义,此处声明
- int main(){
- fa();
- fb();
- std::cin.get(); return 0;
- }
复制代码- // file: b.cpp
- #include <iostream>
- template<typename T>
- void print(T const& m) { std::cout << "b.cpp: " << m << '\n'; }
- void fb() {
- print('2'); // print<char>
- print(0.1); // print<double>
- }
复制代码- a.cpp: 1
- a.cpp: 0.1
- b.cpp: 2
- a.cpp: 0.1
复制代码
|
|
|
|
|
|
|