模板使用不当?

时间:2011-08-16 13:52:12

标签: c++ 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");

这样风格还是与模板相关的性能损失?

6 个答案:

答案 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} }。内联时,它将与非模板版本相同(除非您有机会通过检查结果类型使功能更智能)。