我理解模板有点被归咎于二进制膨胀,我也理解模板只是一种模式。我真的不了解那里的螺母和螺栓。
很多时候我看到类似下面的代码,它返回一个基类指针。
class GameObject
{
public:
Component* getComponent(std::string key);
};
static_cast<Comp>(obj.getComponent("Comp"));
而不是使该方法成为模板方法。
class GameObject
{
public:
template<typename T>
T* getComponent(std::string key);
};
obj.getComponent<Comp>("Comp");
这样风格还是与模板相关的性能损失?
答案 0 :(得分:3)
该方法采用看似实际上是类型(要返回的组件类型)的“键”这一事实向我表明,直到运行时才知道该类型。如果是这种情况,那么像模板这样的编译时机制就不行了。
唯一的选择是返回基类指针。通常在使用此模式时,仅针对基类调用虚方法 - 因此派生类的实际类型无关紧要(因此不需要static_cast或dynamic_cast)。
编辑:
正如PhilCK在评论中指出的那样,这种类型在编译时实际上是已知的。如果是这种情况,则从未真正需要动态类型查找,并且可以使用简单的工厂方法:
class GameObject {
A getComponentA();
B getComponentB();
C getComponentC();
// etc.
}
// which is more or less identical to:
class ComponentFactory {
public:
virtual Component* create() = 0;
};
class GameObject {
std::map<std::string,ComponentFactory> m;
public:
GameObject() {
// presumably a private map has to be populated with string -> factory methods here
}
template<class T>
T* getComponent(const std::string& s) {
// either the static_cast is needed here, or an alternate (messier)
// implementation of getComponent is needed.
// it's still possible for the wrong type to be passed (by mistake)
// and the compiler won't catch it (with static_cast). Since this could lead
// to heap corruption the only safe way with this type of
// implementation is dynamic_cast.
return static_cast<T>(m[s].create());
}
};
// this doesn't even compile:
// return types:
class GameObject {
template <class T>
T* getComponent(const std::string& s) {
if (s == "A") return new A();
else if (s == "B") return new B();
// etc..
else throw runtime_error("bad type");
}
}
所以我看到它有两种选择。
1)使用简单的工厂方法,根本不需要模板模板。 2)使用地图 - &gt;工厂方法实现与dynamic_cast一起(这似乎打败了使用动态类型创建的目的),如果不需要动态类型,实际上是不必要的复杂
答案 1 :(得分:1)
在零级中,不应存在性能差异。
模板化方法会为每个T
创建一个成员函数。这不会使这些代码本身变慢,但由于代码局部性问题,可能会使调用函数更加昂贵,因为强制转换可能会远离调用站点。只有剖析可以告诉你。
可能还有其他瓶颈需要担心,比如通过值而不是const引用传递参数。
Component* getComponent(std::string key);
Component* getComponent(const std::string& key);
此外,此功能可能是const
。
答案 2 :(得分:0)
如果我理解你的例子,那么问题不是模板膨胀二进制文件,而是模板或多态。
简而言之,模板在编译时允许灵活类型,而多态通过使用继承在运行时允许灵活类型。当然还有更多内容。使用哪一个取决于您的实际需求。在你的情况下,我会说它取决于不同类型的组件之间的关系。如果他们共享功能,他们应该有一个共享基类,这里Component
。如果他们没有任何共同点,你应该使用模板。
WRT表现,除非你是实时的,否则通常不会是两者之间选择的主要原因。模板可能生成的二进制文件甚至比常规类更少,因为只有您使用的是实例化的,并且虚函数(用于继承)的开销很小。所以这通常是风格(或最佳实践)的问题。
答案 3 :(得分:0)
编译器将模板代码转换为程序集。如果它对许多类型执行此操作,则会导致组装代码块重复多次。不使用模板可以避免这种情况。或者经常使用模板是非模板函数的包装函数,或者是少量的模板函数。 性能损失将归因于较大的文件。另一方面,由于使用指针/引用而不是对象,公共函数可能更复杂。编译器内联这些内容的机会较少。
膨胀的另一个原因是模板函数在每个使用它们的dll中被实例化。
答案 4 :(得分:0)
Bloat可能是模板的问题,膨胀本身可能会导致性能问题。但是,一个好的编译器可以优化一些膨胀问题,并且有一些编程技术(在需要时)可以避免其余的。
但这不应该是一个问题 - 你的模板功能很可能是微不足道的,因此需要内联。原则上这可能会导致一些膨胀,但由于功能微不足道,“臃肿”的数量也是微不足道的。
模板示例的一个严重问题是它尝试在返回值上重载纯粹的方法。有些语言(例如Ada)可以使用返回类型解决重载,但C ++不是其中之一。
因此,您需要一些其他方法来确定要调用的方法的特定版本。最简单的方法之一(如果只有几种类型需要担心)是分别声明和定义这些方法,每个方法都有不同的名称。
答案 5 :(得分:0)
看来您的使用涉及某种方式。在这种情况下,我会在您控制的模板方法中进行强制转换。
这两种方法的主要区别在于第一个片段不会检查是否有效进行转换(除非您使用dynamic_cast
,否则无法检查)。
在第二个片段中,如果要转换为的类型不是该标识符对象的类型,则该类可以抛出或返回NULL指针。
我认为模板膨胀(如果有的话)是最小的,因为你可以将实际工作委托给非模板助手,而且模板唯一能做的就是执行{{1} }。内联时,它将与非模板版本相同(除非您有机会通过检查结果类型使功能更智能)。