@nrailgun
2015-09-19T15:11:22.000000Z
字数 3034
阅读 3085
程序设计
今天刚好和同学讨论到这个问题,记一下笔记。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_7M6EA9TN
class 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_7M6EA9TN
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;
}
#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 foo
0000000000000000 W _ZN4TestIfE3fooEv
nr@nr-lab ~/workspace/cc % nm bar.o | grep foo
0000000000000000 W _ZN4TestIfE3fooEv
nr@nr-lab ~/workspace/cc % nm a.out | grep foo
00000000004008ce 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.out
100
100
nr@nr-lab ~/workspace/cc % g++ -c main.cc; g++ -c bar.cc; g++ bar.o main.o;
nr@nr-lab ~/workspace/cc % ./a.out
200
200
g++
某些时候可能做出一些你没有要求的“聪明举动”,比如自动内联,这可能导致一个函数变成宏展开,找不到定义。
template class 可以用来实现对于多种类型可用的工具类型,比如 STL,比如 shared_ptr
。但是,并不是所有类型都是为了所有一切类型而产生,很可能他们只需要产生 int
和 float
两种。将其定义定义在头文件将导致编译效率大大降低,不如实例化类型,再做链接(虽然链接其实也很慢)。
template <>
class Test<float>;