我听说过C++模板上下文中的代码膨胀.我知道现代C++编译器并非如此.但是,我想构建一个例子并说服自己.
让我们说我们上课了
template< typename T, size_t N > class Array { public: T * data(); private: T elems_[ N ]; }; template< typename T, size_t N > T * Array::data() { return elems_; }
而且,假设types.h
包含
typedef Array< int, 100 > MyArray;
x.cpp
包含
MyArray ArrayX;
并y.cpp
包含
MyArray ArrayY;
现在,我如何可以验证的代码空间MyArray::data()
是相同的两个ArrayX
和ArrayY
?
还有什么我应该知道并从这个(或其他类似的简单)例子中验证?如果有任何g ++特定提示,我也对此感兴趣.
PS:关于臃肿,我甚至担心最轻微的臃肿,因为我来自嵌入式环境.
另外:如果模板类被显式实例化,情况是否会发生变化?
你问的是错误的问题 - 你的例子中的任何"膨胀"都与模板无关.(你的问题的答案,顺便说一句,是在两个模块中取成员函数的地址,你会发现它们是相同的)
您真正想要问的是,对于每个模板实例化,生成的可执行文件是否会线性增长?答案是否定的,链接器/优化器将做魔术.
编译创建一种类型的exe:
Array< int, 100 > MyArray;
注意产生的exe大小.现在再做一次:
Array< int, 100 > MyArray; Array< int, 99 > MyArray;
等等,对于30个左右的不同版本,绘制生成的exe大小.如果模板像人们想象的那样糟糕,那么每个唯一模板实例化的exe大小将增加固定量.
在这种特定情况下,如果您进行任何类型的优化,您会发现g ++将倾向于内联访问者.这就是有一些小的代码臃肿,但如果调用的开销会更少,这是有争议的.
但是,验证编译内容的一种简单方法是使用该nm
工具.如果我用一个简单的编译代码main()
行使ArrayX::data()
和ArrayY::data()
,然后用编译它-O0
关掉内联,我可以运行nm -C
看在可执行文件中的符号:
% nm -C test 0804a040 B ArrayX 0804a1e0 B ArrayY 08049f08 d _DYNAMIC 08049ff4 d _GLOBAL_OFFSET_TABLE_ 0804858c R _IO_stdin_used w _Jv_RegisterClasses 080484c4 W Array::data() 08049ef8 d __CTOR_END__ 08049ef4 d __CTOR_LIST__ 08049f00 D __DTOR_END__ ...
您将看到Array
符号仅在最终可执行文件中出现一次,即使两个翻译单元中的每一个的目标文件都包含它自己的副本.(该nm
工具也适用于目标文件.您可以使用它来检查它x.o
,y.o
每个都有一个副本Array
.)
如果nm
没有提供足够的细节,您可能还会看一下该objdump
工具.这很像nm
,但是打开调试符号后,它甚至可以显示诸如使用混合源代码行反汇编输出可执行文件之类的内容.
模板与此无关.
考虑这个小程序:
啊:
class a { int foo() { return 42; } };
b.cpp:
#include "a.h" void b() { a my_a; my_a.foo(); }
c.cpp:
#include "a.h" void c() { a my_a; my_a.foo(); }
没有模板,但你有完全相同的问题.在多个翻译单元中定义相同的功能.规则是相同的:在最终程序中只允许存在一个定义,否则编译器将无法确定调用哪个定义,否则指向同一函数的两个函数指针可能指向不同的地址.
模板代码膨胀的"问题"是不同的:如果你创建了很多不同的同一模板实例.例如,使用你的类,这个程序将冒一些代码膨胀的风险:
Array< int, 100 > i100; Array< int, 99 > i99; Array< long, 100 > l100; Array< long, 99> l99; i100.Data(); i99.Data(); l100.Data(); l99.Data();
严格地说,编译器需要创建4个不同的Data
函数实例,每个模板参数对应一个.实际上,只要生成的代码相同,一些(但不是全部)编译器就会尝试将它们合并在一起.(在这种情况下,组件在许多平台上生成Array< int, 100 >
并且Array< long, 100 >
将是相同的,并且该函数也不依赖于数组大小,因此99和100变体也应该生成相同的代码,因此聪明的编译器将合并实例化重新走到一起.
模板没有魔力.他们并没有神秘地"膨胀"你的代码.它们只是为您提供了一个工具,可以让您轻松地从同一模板中创建大量不同的类型.如果您实际使用所有这些类型,则必须为所有这些类型生成代码.与C++一样,您需要为使用的内容付费.如果你同时使用an Array
,an Array
,an Array
和an Array
,那么你会得到四个不同的类,因为你要求的是四个不同的类.如果你不要求四个不同的课程,他们不会花费你任何东西.