2015-12-05 102 views
2

我想知道泛型显式类型实现的类/结构的影响。 [上性能代码/二进制大小]泛型类型与明确类型类/结构

例如,假设我想实现一个元组结构,可以接受这些值类型(整数,浮点,双)的。

有两种方法去做:

使用泛型结构与模板

template <class T> 
struct tuple{ 
    T x,y; 
    //... the rest of methods and operand implementations 
}; 

2-实施副本每种类型的明确

struct tuplef{ 
    float x,y; 
    //... the rest of methods and operand implementations 
}; 

struct tuplei{ 
    int x,y; 
    //... the rest of methods and operand implementations 
}; 

struct tupled{ 
    double x,y; 
    //... the rest of methods and operand implementations 
}; 

在我看来,第一种方法更容易更新和维护,但不安全的时候n个用户试图在第二种方法使用,无论是不是在一些方法实现(这将需要过滤和路由,为不同类型和可能增加一些额外的操作实现)占类型,这将是因为只有特定的安全类型被接受,但用尽处理不同版本的代码来更新方法的实现,并且它是如此冗余并涉及更多行代码。

期待可以在这个不同的角度启发。

注:我第一次用Google搜索,并不能找到太多对此事

编辑:一个多点这里要考虑的是,在第一种方法包括我们要使用的实现文件当使用使用通用类型的成员方法时,类(cpp)是不可避免的,但在第二种情况下,我们可以只包含头文件(h)。似乎这对主题[check this out]有相关影响。

+0

你可以尝试编译一个两个小例子,并检查这样的大小http://blog2.emptycrate.com/content/nobody-understands-c-part-5-template-code-bloat – wizurd

回答

3

当然,二进制大小将有点依赖于编译器/链接器,但我还没有找到一种情况,使用类模板并生成适当的模板实例化实际上使二进制大小膨胀超过手写等效除非你的手写元组通过dylib导出。

链接器在这里做了一个非常出色的工作,以消除多个翻译单元之间的冗余代码。这不是我仅仅认为理所当然的事情。在我以前的工作场所,我们不得不处理一种关于二进制分发大小的非常痴迷的心态,并且必须有效地表明这些具有直接手写等价类的类模板实际上并不比手写等价物增加分配大小。

在某些情况下,任何类型的代码生成都会导致二进制文件膨胀,但这通常适用于将代码生成用作动态分支形式(例如静态或动态多态)的静态替代方案。例如,将std::sort与C的qsort比较。如果您对std::sortqsort连续存储的一堆可轻易构造/可破坏的类型进行排序,那么qsort可能会生成一个较小的二进制数,因为它不涉及代码生成,并且每种类型所需的唯一唯一代码将是比较器。 std::sort将为每种类型生成一个全新的排序函数,处理方式与可能内联的比较器不同。

这就是说,std::sort通常运行速度比qsort快2-3倍,以换取更大的二进制由于交换静态调度动态调度,并在那里你看到的代码生成发挥作用这是典型的 - 当选择之间速度(带代码生成)或更小的二进制大小(不带)。

有一些美学,可能导致您有利于手写版无论如何,像这样:

struct tuplef{ 
    float x,y; 
    //... the rest of methods and operand implementations 
}; 

...但是性能和二进制文件的大小不应该是其中之一。如果你希望这些不同的元组在设计或实现中分离得更多,这种方法会很有用。例如,你可能有一个tupled这要调整其成员和使用SIMD与AOS代表,像这样*:

*不SIMD的一个很好的例子,它从128位XMM寄存器只有好处,但希望足以说明问题。

struct tupled{ 
    ALIGN16 double xy[2]; 
    //... the rest of methods and operand implementations in SIMD 
}; 

...如果你只有一个通用元组,那么这种变化可能会非常笨拙和难以实现。

template <class T> 
struct tuple{ 
    T x,y; 
    //... the rest of methods and operand implementations 
}; 

值得一像这样,你不一定需要让一切类的成员函数的类模板注意。您可以通过更喜欢非会员像这样获得了更大的灵活性和简单性:

typedef tuple<float> tuplef; 
typedef tuple<double> tupled; 

/// 'some_operation' is only available for floating-point tuples. 
double some_operation(const tupled& xy) {...} 
float some_operation(const tuplef& xy) {...} 

...,你现在可以使用普通的旧功能在超载的情况下,其中的some_operation实现需要相互基于发散元组的类型。您也可以省略some_operation的重载,这些重载对于那些没有意义的类型,并获得您所谈论的那种过滤和路由行为。它还有助于防止你的愿望变成一个庞然大物,以支持非会员,并将它从不适用于所有元组的操作中分离出来。

你也可以通过一些更好的技术来实现这一点,同时保持所有的课程都是成员。然而,对于不同类型的元组之间分歧的实现,或者仅适用于特定类型元组的实现,这里的非成员可以帮助保持代码更简单。您可以倾向于应用于所有元组的常见分母操作的成员,并且以相同的方式实现,同时支持非成员用于在元组类型之间进行分叉的操作,例如,

+0

好点。所以你在谈论一种混合方法。我更喜欢使用它们作为成员,因为大多数操作都是操作符实现(+ - */==!= ...),我可以针对每种类型使用不同的方法,但是可以根据通用选项键入? (也就是说,如果float =>只为浮点数编译方法x,并忽略重载的方法(内存保存),还可以对我添加的编辑发表评论吗? – CME64

+1

@ CME64它在模板代码中更深入一点,但你可以使用方法来做这种“过滤/路由”关于模板,是的,通常它们的实现需要在代码生成时可见,并且对其实现的改变确实需要重新编译所有依赖的转换单元。足够的关注,它可能是值得单独实施所有这些元组,你仍然可以使用通用元组来实现一个非泛型元组。 –

+0

嗯,我目前正在开发一个有趣的游戏引擎,并且性能是一个关键。另外我正在尝试分离程序集,以便我不需要重新编译整个代码,这种模板实现可见性可能与此情况相反 – CME64

3

关于业绩,这两种方式将不会有任何差别,因为通用的类型在编译过程中扩大,即编译器会产生相同的结构作为第二个方法,但用其他名字。

因为编译器生成的结构为你的二进制文件的大小取决于你有多少不同类型的代码中使用。如果你使用tuple<int>tuple<double>tuple<char>tuple<float>然后四个不同的结构生成,这意味着相对于两个方法二进制文件会更大。然而,你看到你获得了灵活性,而且维护也更容易(正如你已经说过的)。

如果您看到其中一个案例与其他案例有很大不同,那么将其分开或制作专门的模板,但始终假定您覆盖的不仅仅是三种类型,这样您会发现维护更容易使用模板。

一件事是,因为一切都与模板编译的时候,你不会得到一个运行时错误。即如果您将某个类型传递给模板,它将被编译并运行,或者编译器会给您一个错误。您没有得到正确编译代码并在运行时失败的情况。

+0

你是它们在编译时被计算出来,这将允许编译器通过将生成的类型限制为使用的类型来优化生成的类型,并且生成的重复类似于第二种方法。然而,我相信编译器可能有优化重复使用的能力,而不是仅仅使用不同的类型重复使用它们,并通过最小化存在时不必要的重复实现来节省内存。您错过的另一点是第一种方法是灵活的,但可能无法正确处理不同的类型(需要处理代码)。 – CME64

1

还有第三种选择。使用enable_if成语仅为特定类型启用模板。使用未启用的类型将导致编译器错误。例。

#include <type_traits> 

// base template 
template<typename T, typename = void> 
struct tuple {}; 

template<typename T> 
inline constexpr bool enable() 
{ return std::is_integral<T>::value || std::is_floating_point<T>::value; } 

// enable template for ints/floats, etc. 
template<typename T> 
struct tuple<T, typename std::enable_if<enable<T>()>::type> 
{ 
    T x, y; 
    // other methods here. 
}; 

这种方法结合了通用模板的优点(即,减少重复),同时还保持使用类型的控制。请注意,扩展性有限是有成本的。如果用户想要引入新类型并使用tuple<T>,他们将无法(当然,不提供重复的实现)。

编辑我知道这个答案并不直接回答你的问题,但我仍然认为这是相关的话题,所以我做了它一个社区后,离开它就好了。

+0

这不是直接相关,但有助于防止不需要的类型的防呆。感谢分享这个。 – CME64