@nrailgun
        
        2015-09-19T07:11:22.000000Z
        字数 3034
        阅读 3309
    程序设计
今天刚好和同学讨论到这个问题,记一下笔记。gcc 默认不允许全局标志重定义,g++ 也是一样。C / C++ 的编译单元是文件,(静态)链接时期发现重复定义,那么默认情况下 ld 会报错:
bar.o: In function
Test::foo()':Test::foo()'
bar.cc:(.text+0x0): multiple definition of
main.o:main.cc:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
一般情况下这都是正确的策略,否则 ld 不能链接到正确的实现(动态链接暂时不讨论)。
但是存在另一个问题,C++ template 如果不使用显式实例化,定义是要放在头文件中。按理来说,如果两个 cpp 文件用同一个类型参数实例化了同一个模板类型,理应发生定义冲突。当然,事实上,肯定不会,这点每个人都印证过。
暂时不打算看编译器源代码,后面再说。目前的做法是先做实验: 
 1. 重复定义普通类型; 
 2. 重复实例化 template 类型,检查 obj 和可执行文件全局标志;
Test.hpp:
#ifndef TEST_HPP_7M6EA9TN#define TEST_HPP_7M6EA9TNclass Test {public:float foo();float a, b, c;};float Test::foo() {int i;for (i = 0; i < 100; ++i) {if (i > 100)break;}return i;}#endif /* end of include guard: TEST_HPP_7M6EA9TN */
bar.cc:
#include <iostream>#include "Test.hpp"void bar(){Test t;float i = t.foo();std::cout << i << std::endl;}
main.cc:
#include <iostream>#include "Test.hpp"using namespace std;void bar();int main(int argc, char *argv[]){Test t1;cout << t1.foo() << endl;bar();return 0;}
显然出错不用思考。。。
bar.o: In function
Test::foo()':Test::foo()'
bar.cc:(.text+0x0): multiple definition of
main.o:main.cc:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
显然不会出错,重点是冲突的符号如何处理?
Test.hpp:
#ifndef TEST_HPP_7M6EA9TN#define TEST_HPP_7M6EA9TNtemplate <typename T>class Test {public:T foo();T a, b, c;};template <typename T>T Test<T>::foo() {int i;for (i = 0; i < 100; ++i) {if (i > 100)break;}return i;}#endif /* end of include guard: TEST_HPP_7M6EA9TN */
bar.cc:
#include <iostream>#include "Test.hpp"void bar(){Test<float> t;float i = t.foo();std::cout << i << std::endl;}
main.cc:
#include <iostream>#include "Test.hpp"using namespace std;void bar();int main(int argc, char *argv[]){Test<float> t1;cout << t1.foo() << endl;bar();return 0;}
编译并检查 obj 与可执行文件符号:
nr@nr-lab ~/workspace/cc % g++ -c main.cc; g++ -c bar.cc; g++ main.o bar.o;
nr@nr-lab ~/workspace/cc % nm main.o | grep foo0000000000000000 W _ZN4TestIfE3fooEv
nr@nr-lab ~/workspace/cc % nm bar.o | grep foo0000000000000000 W _ZN4TestIfE3fooEv
nr@nr-lab ~/workspace/cc % nm a.out | grep foo00000000004008ce W _ZN4TestIfE3fooEv
这说明,g++ 自己对 template 的重复符号进行了 strip。虽然 obj 文件中存在重复定义的函数,然而 g++ 删除了重复定义,并且没有报警。这也符合我们日常的经验。
问题又来了,g++ 根据什么删除重复定义呢?然而 g++ 并没有那么神奇,判重的标准仅仅是函数名。
bar.cc:
#include <iostream>template <typename T>class Test {public:T foo();T a, b, c;};template <typename T>T Test<T>::foo() {return 200;}void bar(){Test<float> t;float i = t.foo();std::cout << i << std::endl;}
main.cc:
#include <iostream>using namespace std;template <typename T>class Test {public:T foo();T a, b, c;};template <typename T>T Test<T>::foo() {int i;for (i = 0; i < 100; ++i) {if (i > 100)break;}return i;}void bar();int main(int argc, char *argv[]){Test<float> t1;cout << t1.foo() << endl;bar();return 0;}
虽然其实是两个不同的定义,g++ 还是根据链接顺序 strip 掉了一个。
nr@nr-lab ~/workspace/cc % g++ -c main.cc; g++ -c bar.cc; g++ main.o bar.o;nr@nr-lab ~/workspace/cc % ./a.out100100
nr@nr-lab ~/workspace/cc % g++ -c main.cc; g++ -c bar.cc; g++ bar.o main.o;nr@nr-lab ~/workspace/cc % ./a.out200200
g++ 某些时候可能做出一些你没有要求的“聪明举动”,比如自动内联,这可能导致一个函数变成宏展开,找不到定义。
template class 可以用来实现对于多种类型可用的工具类型,比如 STL,比如 shared_ptr。但是,并不是所有类型都是为了所有一切类型而产生,很可能他们只需要产生 int 和 float 两种。将其定义定义在头文件将导致编译效率大大降低,不如实例化类型,再做链接(虽然链接其实也很慢)。
template <>class Test<float>;