模板使用不当
Bad Use Of Templates?
我知道模板有点被指责为二进制膨胀,我也明白模板只是一个模式。我真的不明白具体的细节。
很多时候,我看到像下面这样的代码,它返回一个基类指针。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");
这是风格上的还是与模板相关的性能损失?
方法接受的"key"实际上似乎是一个类型(要返回的组件的类型),这表明直到运行时才真正知道该类型。如果是这种情况,那么像模板这样的编译时机制就行不通了。
唯一的选择是返回一个基类指针。通常在使用此模式时,只针对基类调用虚方法——因此派生类的实际类型无关紧要(因此不需要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)将map -> factory方法实现与dynamic_cast一起使用(这似乎违背了使用动态类型创建的目的),并且如果不需要动态类型,则实际上不必要地复杂
按零顺序应该不会有性能差异。
模板化方法将为每个T
创建一个成员函数。这并不会使代码本身变慢,但由于代码局部性问题,可能会使函数调用的成本更高,因为强制转换可能在远离调用站点的地方进行。只有分析才能告诉你。
可能还有其他需要担心的瓶颈,比如通过值传递参数而不是通过const引用。
Component* getComponent(std::string key);
Component* getComponent(const std::string& key);
同样,这个函数可能是const
.
如果我正确理解你的例子,问题不是关于模板膨胀二进制文件,而是是否使用模板或多态性。
简而言之,模板在编译时允许灵活的类型,而多态性通过使用继承在运行时允许灵活的类型。当然,还有很多事情要做。使用哪一个取决于你的实际需要。在您的情况下,我会说这取决于不同类型组件之间的关系。如果它们共享功能,它们应该有一个共享的基类,这里是Component
。如果它们没有共同之处,你应该使用模板。
WRT性能,除非您是实时的,否则这通常不会是在两者之间进行选择的主要原因。模板可能比常规类生成更少的二进制,因为只有您使用的才会被实例化,并且虚函数(用于继承)的开销很小。所以这通常是风格(或最佳实践)的问题。
编译器将模板代码转换为程序集。如果对许多类型都这样做,将导致汇编代码块被重复多次。这可以通过不使用模板来避免。或者经常使用模板的是非模板函数的包装函数,或者是少量的模板函数。性能损失是由于较大的文件造成的。另一方面,由于使用指针/引用而不是对象,普通函数可能会更复杂。并且编译器内联这些代码的机会更少。
膨胀的另一个原因是模板函数在使用它们的每个dll中都被实例化。
膨胀可能是模板的一个问题,而膨胀本身会导致性能问题。然而,一个好的编译器可以优化一些膨胀问题,并且有一些编程技术(在需要的时候)可以避免其余的问题。
这在这里不应该是一个问题——你的模板函数可能很琐碎,因此需要内联。原则上,这本身可能会导致一些膨胀,但由于函数是微不足道的,"膨胀"的量也是微不足道的。
模板示例中的一个严重问题是,它试图在返回值上重载纯方法。有些语言(如Ada)可以使用返回类型来解析重载,但c++不是其中之一。因此,您需要一些其他方法来确定您打算调用的方法的特定版本。最简单的方法之一(如果只需要考虑几个类型)是分别声明和定义这些方法,每个方法有不同的名称。
看来你的用法涉及到这样或那样的强制转换。在这种情况下,我将在模板方法中进行强制转换,该方法在您的控制之下。
这两种方法之间的一个主要区别是,第一个代码片段不检查强制转换是否有效(除非使用dynamic_cast
,否则不可能检查)。
在第二个代码片段中,如果要转换到的类型不是该标识符所指的对象的类型,则类可能抛出或返回NULL指针。
我假设模板膨胀(如果有的话)将是最小的,因为无论如何您可以将实际工作委托给非模板助手,模板除了执行static_cast
之外唯一要做的事情。当内联时,它将与非模板版本相同(除了您有机会通过检查结果类型使函数更智能)。
- 如何创建一个CMake变量,除非显式重写,否则使用默认值
- C++:TypeDef使用元组
- 使用std::multimap迭代器创建std::list
- 从不同线程使用int64的不同字节安全吗
- 比较并显示使用最小值(a,b)和最大值(a、b)升序排列的4个数字
- 为什么在全局范围内使用"extern int a"似乎不行?
- 在C#中处理C++指针而不使用unsafe的最佳方法
- 使用C++库在Android项目中修改gradle中的cmake参数,用于插入指令的测试
- 如何使用Google Mock来模拟gettimeofday()
- 如何使用默认参数等选择模板专业化
- 使用宏扩展的泛型:为什么指令缓存使用不当?
- 互斥 c++ 使用不当
- 返回功能的使用不当
- 由于指针使用不当而导致的未上线异常
- 类型特征C++使用不当
- 带有继承的new使用不当
- system()调用使用不当
- 模板使用不当
- c_str使用不当
- 为什么这个initializer_list在传递字符串时使用不当行为