is class templates made in gamedevelopment?

Started by
8 comments, last by Servant of the Lord 11 years, 3 months ago

Hello smile.png

I just learned templates, and i know quite abit right now.

So i just wanted to ask if there is any situation where you want to make your own class template. Becasue vectors already exist, and i cant really imagine when you should even need to define your own class templates.

But i know defining your own function templates are very useful in alot of kinds situations

Advertisement
I don't fully understand templates, but I do know that they are agnostic of the purpose of your code. Game development or otherwise, if a part of your code will benefit from their use, use them. They are commonly used in resource management in game development.

Just make sure to use them where they make sense and for their intended purpose. Overuse is bad. Just because you can, doesn't mean you should.

I'm working on some right now (IDE open in another window), wrapping boost::any and creating a Property type for config files.

Here's one from a 2d API called SFML:

template <typename Type> struct Point2D //Actually it's called 'Vector', but it's a math vector not an array vector. { Type x; Type y; }; typedef Point2D<int> Point2i; //Integer point. typedef Point2D<float> Point2f; //Float point.

(This is a bad example, because it might lead someone to think all their classes should template-define their member variable types - but SFML actually has a reason for doing this)

Don't go template-crazy, but they definitely have their uses. Most classes don't need to be templates, but some do. Most functions don't need to be templates (and should rather be overloaded functions), but some do.

A few months ago I wrote a resizable templated container similar to std::vector but 2D, and that supports negative positions as well as positive.

templates are also used for meta-programming in C++ (C++11 added alot of meta-programming helper classes), where you can write code that runs and does calculations during compilation instead of during the program's execution, which has benefits as well.

Some Manager and Factory styled objects are designed using templates to allow you to have a singleton that can easily dump out any of a given family of resources. Personally I never used template's, everything I ever design is a good OOP design that inherits from base classes and negates any use for a template. I pass objects along as their lowest level base component and then re-cast back to the necessary derivative when needed.

To not be so vague, yes they are used in some cases but not by the majority of engines, technologies or teams that I have seen. XNA does use some template based factory creation methods in it's content pipeline methods and as such some of the indie developed engines around XNA will inherently expose a template based framework as well. Outside of XNA I can't say as that I've ever seen them used in practice.

Dan Mayor

Professional Programmer & Hobbyist Game Developer

Seeking team for indie development opportunities, see my classifieds post

ok smile.png

thanks everybody for the help

Templates can come in handy when you want to create a resource manager that can manage different type of resources. That is what i did in my engine. I have a ResourceManager class which is templated and the i have ResourceManager<Shader>, ResourceManager<Texture> for example.

We use templates for a lot of different things. I am going to (mostly) focus on how templates can prevent you from making errors in your code. I think this is a frequently overlooked when people considers templates.

1) Data Structures. Just because the STL provides some doesn't mean it has all of the data structures you need. In addition, very few commercial game companies use the STL (and you should atleast know why). One simple one that you could write would be a range checking array. If the operator [] functions are inlined a good compiler will optimize it out completely. However, in debug builds you can throw in an assert that will catch these out of range errors that will save you a lot of debugging time. To get you started:


template <typename TYPE, int ARRAY_SIZE>
class RangeArray {
public:
     STATIC_ASSERT(ARRAY_SIZE > 0);
     TYPE& operator[](int idx) { Assert(idx >= 0 && idx < ARRAY_SIZE); return m_array[idx]; }
private:
    TYPE m_array[ARRAY_SIZE];
};

RangeArray<int, 5> myArray;
printf("%d\n", myArray[0]);
int someIndex = 2 * 2 * 2;
int i = myArray[someIndex]; // this will compile, but it will not fail silently at runtime

2) Static assertions are very useful for catching bad code at compile time. If you want to limit the overloads to a certain type by providing all of the possible overloads and throwing a static assertion into the main templated function. Or not even define it at all, just declare it. If you can catch the error at compile time it is a lot better than at runtime. Especially since assertions often aren't of the fatal variety if you are working on a large project. Here's a somewhat practical example, with a Vector3. The range and size of the structure is known and constant, so we can use non type template parameters.


class Vector3 {
public:
    // If the index is not 0, 1, or 2 it is known to be invalid!
    template <int idx> float getElem() const { STATIC_ASSERT_FAIL_MSG("Invalid element index!"); } 
    template <> float getElem<0>() const { return x; }
    template <> float getElem<1>() const { return y; }
    template <> float getElem<2>() const { return z; }
private:
    float x, y, z;
};

Vector3 someVec;
float atElem0 = someVec.getElem<0>(); // OK
float atElem4 = someVec.getElem<5>(); // ERROR will not compile

3) Compilers can inline templates and the optimizations made possible with inlining can be quite remarkable. It is what makes them superior to function pointers (and is largely why std::sort trumps qsort). It's also extremely useful in the form of delegates and functors. Even if you use the stl, you will need to define your own functors all the time to process your custom data.

4) Strings. It is a pain to support code that works with 8 bit, 16 bit, and 32 bit characters. If you ever embark on localization or code that otherwise needs to support both at runtime the only sane way to do it (in my opinion anyways) would be with templates. Certainly better than using the preprocessor all over the place to route it to the equivalent call.

In addition, I frequently don't really know how large statically sized arrays should be when I do string concatenations on c-strings. A frequently unknown benefit of templates is that they can take the size of the array as a parameter. Alloca won't work here because we can't get the string sizes if they aren't constructed yet.


template <typename CHAR_TYPE, int ARRAY_SIZE>
INLINE CHAR_TYPE * string_format(CHAR_TYPE ( & dest) [ARRAY_SIZE], const CHAR_TYPE * fmt, ...) {
    va_list argp;
    ASSERT_ONLY(int numWritten =) vsprintf(dest, fmt, argp); // yes I know this will only work with char and not wchar_t...
    SLOW_ASSERT_MSG(numWritten < ARRAY_SIZE && numWritten > 0, "Array not big enough!");
    va_end(argp);
    return dest;
}

const int kStrArraySize = 256;
char strArray[kStrArraySize];
string_format(strArray, "Entity Position: %f, %f, %f\n", entPos.x, entPos.y, entPos.z);
DebugDrawString(entPos, strArray);
// if we do a bunch of string formatting on this array and make debug drawing calls and it overflows, we can // go back and change it in ONE place.  We don't have to find all of the places we passed kStrArraySize and // change that too.

This will catch an array overrun without silently thrashing memory. Or at least without you having to check the return value on vsprintf every time yourself.

5) Catching errors in casting pointer types. This can be a real lifesaver when you have a message/event system that uses base ptr data. dynamic_cast is almost always a sign of bad design, but don't underestimate your ability to make a mistake when casting. NOTE: dynamic_cast requires the from type has a vtable!



// this code assumes LPTR_TYPE and RPTR_TYPE are pointer types, although there is a template for that too :)
template <typename LPTR_TYPE, typename RPTR_TYPE> 
FORCE_INLINE LPTR_TYPE smart_cast(RPTR_TYPE right) { 
#if _CPPRTTI 
    Assert(!right || dynamic_cast<LPTR_TYPE>(right)); 
#endif 
    return static_cast<LPTR_TYPE>(right); 
}

This was extremely useful for pool allocated data seen in message passing and event systems.

6) Also great when making assignments between numerical types with different bits of precision. For example, lets say we have a U32 but need to pass it as U16.


template <typename LTYPE, typename RTYPE>
LTYPE& Assign(LTYPE& assignedTo, const RTYPE assigned) {
    assignedTo = assigned;
    Assert(assignedTo == assigned); // this will fail if there weren't enough bits in LTYPE to store RTYPE
    return assignedTo;
}

   // example code that isn't really practical
   U32 src = U16_MAX + 5;
   U16 dest = 0; 
   Assign(dest, src); // this will not fail silently anymore

7) Other things I have found templates to be useful for.

- Component Entity Systems

- Your own RTTI system (this will usually require the preprocessor, a virtual function, and a hash code too)

- Permutations/Shuffling in vector libraries

- Some of my coworkers love it for serialization code. That's not really something I can say I'm an expert on but leaving it here anyways.

- Static Assertions themselves

- All that metaprogramming stuff

- enum range checking at compile time

- avoiding virtual functions

- a bunch of other things I haven't mentioned...

But don't underestimate how much time it will save you from the errors it can catch in your code. Most of this should compile out cleanly in release builds!

<shameless blog plug>
A Floating Point
</shameless blog plug>

Very excellent post!

1) Data Structures. Just because the STL provides some doesn't mean it has all of the data structures you need. In addition, very few commercial game companies use the STL (and you should atleast know why).

Just for clarification and summary of the EASTL page that was linked to: EASTL is more directed towards development of triple-A games and to games targeting game consoles. Indie game developers for PC/Mac/Linux are perfectly fine when using the standard library.

Since I'm still using VS2008 (I will update soon... Soon... Soon...) I don't have access to std::unique_ptr, which means that I have to write RAII templates for API objects that are destroyed by things other than delete.

I'm looking at YOU, COMReleasable...
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

You could use boost::unique_ptr. smile.png

This topic is closed to new replies.

Advertisement