[关闭]
@nrailgun 2015-09-19T15:11:22.000000Z 字数 3034 阅读 3085

C++ template 多重定义与实例化

程序设计


C++ 重定义问题

今天刚好和同学讨论到这个问题,记一下笔记。gcc 默认不允许全局标志重定义,g++ 也是一样。C / C++ 的编译单元是文件,(静态)链接时期发现重复定义,那么默认情况下 ld 会报错:

bar.o: In function Test::foo()':
bar.cc:(.text+0x0): multiple definition of
Test::foo()'
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:

  1. #ifndef TEST_HPP_7M6EA9TN
  2. #define TEST_HPP_7M6EA9TN
  3. class Test {
  4. public:
  5. float foo();
  6. float a, b, c;
  7. };
  8. float Test::foo() {
  9. int i;
  10. for (i = 0; i < 100; ++i) {
  11. if (i > 100)
  12. break;
  13. }
  14. return i;
  15. }
  16. #endif /* end of include guard: TEST_HPP_7M6EA9TN */

bar.cc:

  1. #include <iostream>
  2. #include "Test.hpp"
  3. void bar()
  4. {
  5. Test t;
  6. float i = t.foo();
  7. std::cout << i << std::endl;
  8. }

main.cc:

  1. #include <iostream>
  2. #include "Test.hpp"
  3. using namespace std;
  4. void bar();
  5. int main(int argc, char *argv[])
  6. {
  7. Test t1;
  8. cout << t1.foo() << endl;
  9. bar();
  10. return 0;
  11. }

显然出错不用思考。。。

bar.o: In function Test::foo()':
bar.cc:(.text+0x0): multiple definition of
Test::foo()'
main.o:main.cc:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status

重复定义 template 类型

显然不会出错,重点是冲突的符号如何处理?

Test.hpp:

  1. #ifndef TEST_HPP_7M6EA9TN
  2. #define TEST_HPP_7M6EA9TN
  3. template <typename T>
  4. class Test {
  5. public:
  6. T foo();
  7. T a, b, c;
  8. };
  9. template <typename T>
  10. T Test<T>::foo() {
  11. int i;
  12. for (i = 0; i < 100; ++i) {
  13. if (i > 100)
  14. break;
  15. }
  16. return i;
  17. }
  18. #endif /* end of include guard: TEST_HPP_7M6EA9TN */

bar.cc:

  1. #include <iostream>
  2. #include "Test.hpp"
  3. void bar()
  4. {
  5. Test<float> t;
  6. float i = t.foo();
  7. std::cout << i << std::endl;
  8. }

main.cc:

  1. #include <iostream>
  2. #include "Test.hpp"
  3. using namespace std;
  4. void bar();
  5. int main(int argc, char *argv[])
  6. {
  7. Test<float> t1;
  8. cout << t1.foo() << endl;
  9. bar();
  10. return 0;
  11. }

编译并检查 obj 与可执行文件符号:

  1. nr@nr-lab ~/workspace/cc % g++ -c main.cc; g++ -c bar.cc; g++ main.o bar.o;
  1. nr@nr-lab ~/workspace/cc % nm main.o | grep foo
  2. 0000000000000000 W _ZN4TestIfE3fooEv
  1. nr@nr-lab ~/workspace/cc % nm bar.o | grep foo
  2. 0000000000000000 W _ZN4TestIfE3fooEv
  1. nr@nr-lab ~/workspace/cc % nm a.out | grep foo
  2. 00000000004008ce W _ZN4TestIfE3fooEv

这说明,g++ 自己对 template 的重复符号进行了 strip。虽然 obj 文件中存在重复定义的函数,然而 g++ 删除了重复定义,并且没有报警。这也符合我们日常的经验。

删除重复定义的根据

问题又来了,g++ 根据什么删除重复定义呢?然而 g++ 并没有那么神奇,判重的标准仅仅是函数名。

bar.cc:

  1. #include <iostream>
  2. template <typename T>
  3. class Test {
  4. public:
  5. T foo();
  6. T a, b, c;
  7. };
  8. template <typename T>
  9. T Test<T>::foo() {
  10. return 200;
  11. }
  12. void bar()
  13. {
  14. Test<float> t;
  15. float i = t.foo();
  16. std::cout << i << std::endl;
  17. }

main.cc:

  1. #include <iostream>
  2. using namespace std;
  3. template <typename T>
  4. class Test {
  5. public:
  6. T foo();
  7. T a, b, c;
  8. };
  9. template <typename T>
  10. T Test<T>::foo() {
  11. int i;
  12. for (i = 0; i < 100; ++i) {
  13. if (i > 100)
  14. break;
  15. }
  16. return i;
  17. }
  18. void bar();
  19. int main(int argc, char *argv[])
  20. {
  21. Test<float> t1;
  22. cout << t1.foo() << endl;
  23. bar();
  24. return 0;
  25. }

虽然其实是两个不同的定义,g++ 还是根据链接顺序 strip 掉了一个。

  1. nr@nr-lab ~/workspace/cc % g++ -c main.cc; g++ -c bar.cc; g++ main.o bar.o;
  2. nr@nr-lab ~/workspace/cc % ./a.out
  3. 100
  4. 100
  1. nr@nr-lab ~/workspace/cc % g++ -c main.cc; g++ -c bar.cc; g++ bar.o main.o;
  2. nr@nr-lab ~/workspace/cc % ./a.out
  3. 200
  4. 200

g++ 自动内联

g++ 某些时候可能做出一些你没有要求的“聪明举动”,比如自动内联,这可能导致一个函数变成宏展开,找不到定义。

template 实例化

template class 可以用来实现对于多种类型可用的工具类型,比如 STL,比如 shared_ptr。但是,并不是所有类型都是为了所有一切类型而产生,很可能他们只需要产生 intfloat 两种。将其定义定义在头文件将导致编译效率大大降低,不如实例化类型,再做链接(虽然链接其实也很慢)。

  1. template <>
  2. class Test<float>;
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注