当前位置:  开发笔记 > 编程语言 > 正文

C++模板:说服自己反对代码膨胀

如何解决《C++模板:说服自己反对代码膨胀》经验,为你挑选了3个好方法。

我听说过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()是相同的两个ArrayXArrayY

还有什么我应该知道并从这个(或其他类似的简单)例子中验证?如果有任何g ++特定提示,我也对此感兴趣.

PS:关于臃肿,我甚至担心最轻微的臃肿,因为我来自嵌入式环境.


另外:如果模板类被显式实例化,情况是否会发生变化?



1> Terry Mahaff..:

你问的是错误的问题 - 你的例子中的任何"膨胀"都与模板无关.(你的问题的答案,顺便说一句,是在两个模块中取成员函数的地址,你会发现它们是相同的)

您真正想要问的是,对于每个模板实例化,生成的可执行文件是否会线性增长?答案是否定的,链接器/优化器将做魔术.

编译创建一种类型的exe:

Array< int, 100 > MyArray;

注意产生的exe大小.现在再做一次:

Array< int, 100 > MyArray;
Array< int, 99 > MyArray;

等等,对于30个左右的不同版本,绘制生成的exe大小.如果模板像人们想象的那样糟糕,那么每个唯一模板实例化的exe大小将增加固定量.


"在两个模块中获取成员函数的地址,你会看到它们是相同的" - 这会调用Schroedingers编译器.通过观察事物,你可以改变它.特别是取一些东西的地址限制了编译器可以用它做什么.例如,如果需要int*,则不能将int放入寄存器中.
@MSalters:有点无意义,因为C++是围绕可观察行为定义的.如果你没有观察C++程序,编译器可能只发出一个`nop`.

2> Boojum..:

在这种特定情况下,如果您进行任何类型的优化,您会发现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::data()符号仅在最终可执行文件中出现一次,即使两个翻译单元中的每一个的目标文件都包含它自己的副本.(该nm工具也适用于目标文件.您可以使用它来检查它x.o,y.o每个都有一个副本Array::data().)

如果nm没有提供足够的细节,您可能还会看一下该objdump工具.这很像nm,但是打开调试符号后,它甚至可以显示诸如使用混合源代码行反汇编输出可执行文件之类的内容.



3> jalf..:

模板与此无关.

考虑这个小程序:

啊:

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,那么你会得到四个不同的类,因为你要求的是四个不同的类.如果你不要求四个不同的课程,他们不会花费你任何东西.


@Josh:翻译单元将包含相同代码的实例(我的非模板示例也是如此),是的,但最终的可执行文件肯定不会.您的链接甚至明确地说明了这一点:"不知何故,编译器和链接器必须确保每个模板实例在可执行文件中只发生一次(如果需要),否则根本不会".如果编译器生成了多个实例化(如"不执行任何操作"模型),则链接器会删除除其中一个之外的所有实例,就像使用非模板符号一样.
推荐阅读
Gbom2402851125
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有