本文主要讨论的是在模板函数代码采用内嵌模式进行组织的时候的调用时候的连接问题。
本文采用的编译器为:g++-4.4.real (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3
在模板在编译单元中被调用的时候,如果能找到模板的定义,则会对其进行实例化,因此当模板使用嵌入式进行组织代码的时候(即模板的声明和定义均放在.h文件中),当这个文件被引用在不同的编译单元的时候,会被编译多次,生成多个实例。
如果生成了多个实例,那么为什么在连接的时候没有报连接错误呢,这个关键在于编译器在生成代码的时候,给模板实例的符号赋予的性质有关系,通过系统工具nm查看发现模板函数实例化后的性质为“W”(未明确指定的弱链接符号;同链接的其他对象文件中有它的定义就用上,否则就用一个系统特别指定的默认值。)。因此连接器在连接的时候只是用第一个而忽视其他实例。详细情况见下面实例。
自己做的一个实验
// template1.h
1// basics/myfirst2.hpp
2
3#ifndef TEMPLATE_H
4#define TEMPLATE_H
5
6#include <iostream>
7#include <typeinfo>
8
9
10// declaration of template
11template <typename T>
12void print_typeof (T const&);
13
14// implementation/definition of template
15template <typename T>
16void print_typeof (T const& x)
17 {
18 std::cout << typeid(x).name() << std::endl;
19 }
20
21#endif // TEMPLATE_H
// use_template.h
1#ifndef USE_TEMPLATE_H
2#define USE_TEMPLATE_H
3void use_template(void);
4static void use_template2(void);
5#endif // USE_TEMPLATE_H
// use_template.cpp
1#include <iostream>
2#include "use_template.h"
3#include "template1.h"
4using std::cout;
5void use_template(void) {
6 int i = 0;
7 print_typeof(i);
8 }
9void use_template2(void) {
10 inti = 0;
11 print_typeof(i);
12 }
// use_template_main.cpp
1#include <iostream>
2#include <typeinfo>
3#include "use_template.h"
4// #include "template1.h"
5using std::cout;
6 // 具体用哪个.o文件中的print_typeof函数由编译时候,输入的文件顺序决定
7template <typename T>
8void print_typeof (T const& x)
9 {
10 std::cout << typeid(x).name() << std::endl<<"this is in main file" << std::endl;
11 }
12/*
13// 会报和上面重复定义
14template <typename T>
15void print_typeof (T const& x)
16 {
17 std::cout << typeid(x).name() << std::endl<<"this is in main file" << std::endl;
18 }
19*/
20// 不会报重复定义,use_template中用的是模板生成的,而本文件中的print_typeof则用的是当前函数
21// void print_typeof (int const& x)
22// {
23// std::cout << typeid(x).name()<< std::endl << "this is in main file" <<std::endl;
24// }
25int main (int argv, char *argc[]) {
26 use_template();
27 inti = 0;
28 print_typeof(i);
29 }
30// 不会被用到
31void print_typeof (int const& x)
32 {
33 std::cout << typeid(x).name() << std::endl <<"this is in main file end" << std::endl;
34 }
// 结果1
ubuntu:~/C++/template$g++ use_template.cpp use_template_main.cpp
ubuntu:~/C++/template$./a.out
i
i
// 结果2
ubuntu:~/C++/template$g++ use_template_main.cpp use_template.cpp
ubuntu:~/C++/template$./a.out
i
thisis in main file
i
thisis in main file
用nm查两个点o文件结果如下:
ubuntu:~/C++/template$g++ use_template_main.cpp use_template.cpp -c
ubuntu:~/C++/template$nm use_template.
use_template.cpp use_template.h use_template.o
ubuntu:~/C++/template$nm use_template.o
00000074t _GLOBAL__I__Z12use_templatev
00000000 W _Z12print_typeofIiEvRKT_
00000000T _Z12use_templatev
00000034t _Z41__static_initialization_and_destruction_0ii
0000001at _ZL13use_template2v
00000000W _ZNKSt9type_info4nameEv
U _ZNSolsEPFRSoS_E
U _ZNSt8ios_base4InitC1Ev
U _ZNSt8ios_base4InitD1Ev
U _ZSt4cout
U_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
00000000b _ZStL8__ioinit
U_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
U _ZTIi
U __cxa_atexit
U __dso_handle
U __gxx_personality_v0
ubuntu:~/C++/template$nm use_template_main.o
000000bet _GLOBAL__I_main
00000000 W _Z12print_typeofIiEvRKT_
00000029T _Z12print_typeofRKi
U _Z12use_templatev
0000007et _Z41__static_initialization_and_destruction_0ii
00000000W _ZNKSt9type_info4nameEv
U _ZNSolsEPFRSoS_E
U _ZNSt8ios_base4InitC1Ev
U _ZNSt8ios_base4InitD1Ev
U _ZSt4cout
U_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
00000000b _ZStL8__ioinit
U_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
U _ZTIi
U __cxa_atexit
U __dso_handle
U __gxx_personality_v0
00000000T main
根据nm的man中的说明,W表示:未明确指定的弱链接符号;同链接的其他对象文件中有它的定义就用上,否则就用一个系统特别指定的默认值。(从上述结果来看,如果有多个实现,则从中选取一个,所有参与连接的文件都用同一个实现。)