开发者

Bad Use Of Templates?

I understand that templates kinda get blamed for binary bloat, I also understand that a template is just a pattern. I dont really understand the nuts and bolts as it where.

Alot of time I see code like the following where it returns a base class pointer.

class GameObject
{
public:
    Component* getComponent(std::string key);
};

static_cast<Comp>(obj.getComponent("Comp"));

Rather than making the method a template method.

class GameObject
{
public:
    template<typename T>
    T* getComponent(std::string key);
};

obj.getComponent开发者_如何学C<Comp>("Comp");

Is this stylistic or is there a performance loss associated with the templates?


The fact that the method takes the "key" which appears to be really a type (the type of component to return) indicates to me that the type is not really known until runtime. If that's the case then a compile time mechanism like templates will not do.

The only option is to return a base class pointer. And typically when this pattern is used, only virtual methods are called against the base class--so the actual type of the derived class doesn't matter (so no static_cast or dynamic_cast is needed).

Edit:

As PhilCK noted in comments, the type is actually known at compile time. If that is the case then dynamic type lookup was never really needed, and simple factory methods could have been used:

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");
    }
}

So there are 2 choices the way I see it.

1) use simple factory methods in which case templates are not needed at all. 2) use map -> factory method implementation together with dynamic_cast (which seems to defeat the purpose of using dynamic type creation) and is actually unnecessarily complex if dynamic types aren't needed


In zeroth order there shouldn't be a performance difference.

The templated method will create a member function for each T though. This doesn't make this code slower per se, but might make the invocation the function more expensive due to code locality issues since the cast is probably done further away from the call site. Only profiling can tell you.

There are probably other bottlenecks to worry about, like passing arguments by value rather than by const reference.

Component* getComponent(std::string key);
Component* getComponent(const std::string& key);

Also, this function can probably be const.


If I understand your example correctly, the question is not about templates bloating binaries, but whether to use templates or polymorphism.

In short, templates allow flexible types at compile time, while polymorphism allow flexible types at run-time, by using inheritance. There's a lot more to it, of course. Which one to use depends on your actual needs. In your case, I'd say it depends on the relation between the different types of components. If they share functionality, they should have a shared base class, here Component. If they have nothing in common, you should use templates.

WRT performance, unless you're into real-time, that usually won't be the main reason for choosing between the two. Templates might generate even less binary then regular classes, because only what you use is instantiated, and the overhead of virtual functions (used in inheritance) is slim. So this is usually a matter of style (or best practice).


The compiler converts the template code into assembly. If it does this for many types it will lead to blocks of assembly code being repeated many times. This can be avoided by not using templates. Or often using templates are wrapper functions to non-template functions, or a small number of template functions. The performance loss would be due to the larger files. On the other hand the common function may be more complex because of the use of pointers/references instead of objects. And there is less opportunity for the compiler to inline these.

Another reason for bloat is that the template function are instantiated in every dll that they are used.


Bloat can be an issue with templates, and bloat in itself can cause performance issues. However, a good compiler can optimise some bloat issues away, and there are programming techniques that (when needed) can avoid the rest.

That shouldn't be an issue here, though - your template function is likely to be trivial, and therefore to be inlined. That can in principle cause some bloat in itself, but since the function is trivial, the amount of "bloat" is trivial too.

A serious problem with your template example is that it tries to overload a method purely on the return value. There are languages (such as Ada) that can resolve overloads using the return type, but C++ isn't one of them.

Therefore, you need some other way to determine which particular version of the method you intend to call. One of the simplest ways (if there's only a few types to worry about) is to declare and define these methods separately, each with a different name.


It appears your usage involves a cast one way or another. In this case I'd do the cast in the template method which is under your control.

A major difference between the two approaches is that the first snippet does not check if it is valid to do the cast (and unless you use dynamic_cast it is impossible to check).

In the second snippet it is possible for the class to throw or return a NULL pointer, if the type to cast to is not the type of the object by that identifier.

I would assume the template bloat, if any, would be minimal, as you could delegate the real work to a non-template helper anyway, and the only thing the template would do in addition is to perform the static_cast. When inlined, it will be the same as the non-template version (except you have a chance to make the function smarter by checking the result type).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜