通过继承减少模板膨胀

时间:2010-06-14 13:07:17

标签: c++ inheritance templates code-generation

有没有人有使用继承减少模板代码膨胀的经验?

我犹豫以这种方式重写我们的容器:

class vectorBase
{
  public:
    int size();
    void clear();
    int m_size;
    void *m_rawData;
    //....
};

template< typename T > 
class vector : public vectorBase
{
    void push_back( const T& );
    //...

};

我应该在减少编译时间的同时保持最佳性能

我也想知道为什么stl实现不使用这种方法

感谢您的反馈

7 个答案:

答案 0 :(得分:3)

如果您不知道存储元素的类型,则对矢量的操作很少有意义。例如,添加到基类的clear()方法需要调用从向量中删除的元素的析构函数,因此需要知道它们的类型并需要进行模板化。

对于void *m_rawData而言,如果不了解其中的内容类型,基本上所有操作都至少必须知道存储类型的大小。我唯一能想到的就是你可以free()如果你知道它不包含任何元素(如果它包含你必须调用它们的析构函数的元素)。如果您不知道各个元素的开始和结束位置,则分配,设置和访问元素都不起作用。如果m_rawData是正确键入的T*,那么所有方法的实现将更加简洁和简单。

基类中的size()方法只有在其唯一的工作是返回m_size成员变量时才有效,但向量不一定要显式存储大小(实现I)知道不要)。你可能实现的是大小是显式存储的,但是再次size()可能不是一个需要很长时间才能编译的方法,即使它是模板化的。

总之,我不认为有很多方法可以在基类中实现。向量上的大多数操作需要知道存储在其中的元素。

答案 1 :(得分:1)

我认为这是一个不成熟的优化。通常除了嵌入式系统之外,磁盘空间和内存都很丰富且便宜,因此没有理由尝试针对少量代码空间进行优化。通过将它全部保存在模板代码中,它使得更明显的是发生了什么,而不是使用会使事情变得复杂的继承。

此外,大多数应用程序不会生成数百个实例,并且对于每个应用程序,并非所有方法都可以使用,从而进一步减少了代码占用空间。

只有存在非常严格的内存考虑因素(嵌入式)时,才会考虑不同的可能方法(包括您提供的方法)。

编辑:我不确定在一些标准容器案例中有多少收获,因为它们仍然需要大量的模板代码。对于只有少量模板特定代码和许多通用逻辑的内部类,这绝对有助于生成代码和编译速度。我怀疑它不经常使用,因为它更复杂,而且好处仅限于某些情况。

答案 2 :(得分:1)

我理解你的方法。

坦率地说,我已经使用过它了......虽然显然不适用于STL容器:它们的代码实际上没有错误并且已经过优化,而且我不太可能自己想出更好的实现方法!

我不太关心编译时间:这是一个令人尴尬的并行问题(除了链接)和distcc等,即使使用大型代码库也可以解决所有问题。我的意思是大,我在一家需要HP新编译器的公司工作,因为我们的版本在链接器的命令行中不支持128Ko以上。它只是应用程序中的一个,而且是在几年前,他们幸好自从将它拆分成几个块。

然而,尽管我不关心编译时间,但我非常关心减少依赖性和二进制兼容性。因此,当我编写自己的模板化代码时,我会检查是否可以将某些操作考虑在模板化代码之外。

首要任务是隔离那些你真正可以获得的地方。获取一行代码并不值得花时间,您希望获得完整的功能。

第二项任务是决定是否要让它们内联。这取决于你是否关心性能,函数调用的开销对你来说可能重要,也可能不重要。

但是我当然不会使用继承来完成工作。 InheritanceIS-A关系:它定义了一个接口,而不是一个实现。要么使用Composition,要么只是释放存储在实用程序命名空间中的函数({1}},就像在Boost中一样?)。

答案 3 :(得分:0)

IIRC,Qt对他们的QList等人使用(或使用过)类似的方法。

基本上,它会起作用,但你必须确保将依赖于T的所有内容放在矢量模板中。不幸的是,除了分配原始存储和T / {{1}之外}。我不确定它会得到回报,所以请仔细检查。

当你可以从一些模板参数中抽象出来时(例如size()不需要知道集合的比较器)或者你是否可以为一大类类型酿造一个完整的实现(例如, 。与琐碎的复制者和dtor)。

答案 4 :(得分:0)

您发布的代码完全错误。如果您在向量中存储的类具有析构函数,则不会调用该析构函数,因为编译器vectorBase已丢失有关何时通过强制转换为void*来调用析构函数的所有信息。 / p>

为了正确地执行此操作,调用正确的析构函数,您需要生成每个调用正确析构函数的代码的不同副本,并且通过使用模板简化了这项工作。

(要使用非模板基类的方法,您需要生成尽可能多的机器代码,但是您需要手动编写更多的C ++代码。)

这就是为什么这种方法真的不值得。

答案 5 :(得分:0)

缺点:

是的,这种方法[可能]在有限的专业环境中工作。我并不怀疑std::vector(或其他STL)属于这些情况。

很长一段时间:

正如其他人提到的那样(我同意),在嵌入式系统之外,编译二进制文件中的代码膨胀并不是很大的问题。

但是,我们中的许多人在编译步骤中遇到编译量级代码的编译成本比编译库要链接的编译成本(而不是编译头文件)。添加它的难度是更改其中一个模板化头文件并观察整个项目从头开始重新编译。漫长的编译时间使悲伤的开发人员:(

它可能不会影响大部分开发人员 - 具体取决于公司代码库的大小,以及构建环境的方式。它肯定会给我们公司带来麻烦。

正如一些答案所指出的那样,从std::vector中抽象出来并没有太多可以在你的例子中做得更好。当然,您需要能够创建和销毁单个元素,并使任何方法virtual都会妨碍运行时性能(这比编译时性能更重要)。此外,编译器将无法针对模板化代码优化void*库,这可能会导致大量性能损失。

我对更复杂的结构更感兴趣 - std::map会从抽象中受益吗?如果您将红黑树(SGI实现)取出并链接到红黑树库,该怎么办?您可能(可能)需要将元素存储在std::map之外,因此它不需要调用析构函数,但这可能(再次)导致由于间接加倍而导致运行时性能下降。 / p>

我很确定你不能使用这种方法来改进STL的实现。

如果您对存储的数据结构有更好的了解,或者您有一个非常具体的模板类型(不一定是容器),那么您可能会提高性能。但是,问题就变成了 - 你多久会使用这种新的模板类型,以便编译它的开销会明显改善?当然,它有助于std::vector的编译时间,但可能不适用于my_domain_specific_ptr_container。如果为my_domain_specific_ptr_container节省了50%的编译时间,那么你需要多少次使用它来注意足够大的构建提升来证明增加类的复杂性(以及降低调试能力)。

如果您还没有,那么分配构建工具的时间可能会更好:)

另一方面,如果您尝试这个并且它适用于STL容器...请回发。我想要更快的构建! ;)

答案 6 :(得分:0)

某些实现执行使用(一种形式)上述方法。这是GCC的

  template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
    class vector : protected _Vector_base<_Tp, _Alloc>
    {
    ... 
    }

在这种情况下,目标是将内存管理委派给_Vector_base。如果您确实选择花时间重新创建STL,请在此处跟进您的结果。也许你的努力将有助于结束不时听到的旧“代号膨胀”的呐喊。

相关问题