c++函数模板声明与定义相分离
2016-02-19 10:43
453 查看
最近在仿写stl,发现stl源码中将模板的声明与定义写在一起实在很不优雅。自己尝试用“传统”方法,及在.h文件里声明,在.cpp文件里定义,然后在main函数里包含.h头文件,这样会报链接错误。这是因为函数模板要被实例化后才能成为真正的函数,在使用函数模板的源文件中包含函数模板的头文件,如果该头文件中只有声明,没有定义,那编译器无法实例化该模板,最终导致链接错误。
上面这句话有点抽象。要理解为什么会出错,首先要理解用传统方法写非模板函数时,编译器是怎么运作的。举个例子
编译时会生成两个obj文件,main.obj和test.obj,而在main.obj里并没有f函数的二进制代码,这些代码实际存在于test.obj中。在main.obj中对f的调用只会生成一行call指令,call指令的地址由链接器生成。
再看一个函数模板的例子
我们知道模板有个具现化的过程,在未被使用的时候是不会生成二进制文件的。所以当链接器去找f函数的地址时,因为在这之前没有调用过f(),test.obj里自然就没有f函数的二进制代码,于是就会报错。
要使模板声明与定义分开也不是没有办法。
第一种办法是在main函数里包含cpp文件
这样三个文件的内容通过include实际上包含在同一个文件里,自然就不会出错了。同理,还可以这样
这两种方法实际上都是包含编译,没有本质的区别,不过感觉第二种方法看起来比较舒服。还有一种比较hack的方法
这样由于在test.cpp里实例化了A<int>,所以链接器能够找到相关代码,就不会报错。但是这样main函数要用哪种类型的模板都得在test.cpp先实例化,很不方便。
以上都是包含编译的方法,其实C++0x有个export关键字,使模板能分离编译,但是基本没有编译器支持,在c++11中就废除掉了,所以这里就不提了。
上面这句话有点抽象。要理解为什么会出错,首先要理解用传统方法写非模板函数时,编译器是怎么运作的。举个例子
//---------------test.h-------------------// void f();//这里声明一个函数f //---------------test.cpp--------------// #include”test.h” void f() { …//do something } //这里实现出test.h中声明的f函数 //---------------main.cpp--------------// #include”test.h” int main() { f(); //调用f }
编译时会生成两个obj文件,main.obj和test.obj,而在main.obj里并没有f函数的二进制代码,这些代码实际存在于test.obj中。在main.obj中对f的调用只会生成一行call指令,call指令的地址由链接器生成。
再看一个函数模板的例子
//-------------test.h----------------// template<class T> class A { public: void f(); //这里只是个声明 }; //---------------test.cpp-------------// #include”test.h” template<class T> void A<T>::f() { …//do something } //---------------main.cpp---------------// #include”test.h” int main() { A<int> a; a. f(); }
我们知道模板有个具现化的过程,在未被使用的时候是不会生成二进制文件的。所以当链接器去找f函数的地址时,因为在这之前没有调用过f(),test.obj里自然就没有f函数的二进制代码,于是就会报错。
要使模板声明与定义分开也不是没有办法。
第一种办法是在main函数里包含cpp文件
//-------------test.h----------------// template<class T> class A { public: void f(); //这里只是个声明 }; //---------------test.cpp-------------// #include”test.h” template<class T> void A<T>::f() { …//do something } //---------------main.cpp---------------// #include”test.cpp” int main() { A<int> a; a. f(); }
这样三个文件的内容通过include实际上包含在同一个文件里,自然就不会出错了。同理,还可以这样
//-------------test.h----------------// template<class T> class A { public: void f(); //这里只是个声明 }; #include<test_impl.h> //---------------test_impl.h-------------// template<class T> void A<T>::f() { …//do something } //---------------main.cpp---------------// #include”test.h” int main() { A<int> a; a. f(); }
这两种方法实际上都是包含编译,没有本质的区别,不过感觉第二种方法看起来比较舒服。还有一种比较hack的方法
//-------------test.h----------------// template<class T> class A { public: void f(); //这里只是个声明 }; //---------------test.cpp-------------// #include”test.h” template<class T> void A<T>::f() { …//do something } template class A<int>; //---------------main.cpp---------------// #include”test.h” int main() { A<int> a; a. f(); }
这样由于在test.cpp里实例化了A<int>,所以链接器能够找到相关代码,就不会报错。但是这样main函数要用哪种类型的模板都得在test.cpp先实例化,很不方便。
以上都是包含编译的方法,其实C++0x有个export关键字,使模板能分离编译,但是基本没有编译器支持,在c++11中就废除掉了,所以这里就不提了。
相关文章推荐
- C语言深度解剖读书笔记
- c++ 设计模式4 (Strategy)
- C与C++中使用带默认值的参数
- Typical memory leak (C++中典型的内存泄露)
- 纯C语言实现简单继承机制
- 转:C语言面试题大汇总 (图像处理方向)
- &#10084;&#65039;C++的继承
- &#10084;&#65039;C++基础语法
- &#10084;&#65039;C++应用场景
- &#10084;&#65039;C++语言特点:
- utilities——C++常用仿函数
- C++模板学习
- C++ vector
- 判断素数
- C语言实现单链表逆序与逆序输出实例
- 求1+2+3+4+....+100
- 数字按照大小排列
- C++编译器生成的构造函数的总结
- 春节后第一波大优惠来袭!!史上最全最强C语言视频课程(全程字幕 + 习题作业)
- 在C++中如何使用msgpack进行对象的序列化