|
"Faking" Polymorphism with C++ Templates
Submitted by |
This is a general high-level tip aimed at beginner to intermediate C++
programmers.
Often you'll have a set of datatypes that share a common set of operations. One
such example is a polygon. Polygons can take several forms. For instance you
might have one polygon class that stores vertices itself (for simplicity - these
are useful for "worker" polygons). You might have another polygon class that
holds an array of indices into a global vertex store. You may even have yet
another polygon class that stores an index into a global buffer of indices into
the vertex buffer (this is useful to take advantage of DX8 Index Buffers). In
addition, different polygon classes may have different information associated
with rendering and so on (texture coordinates, lightmaps, etc).
Now say you want to have a method to clip a polygon against a plane. There are
two typical ways to do this in C++. One of which is to implement a separate Clip
method for each polygon class. This is not very good because there will be a lot
of common code between implementations, and if you find a bug in one you'll have
to fix it in all of the others. Another option is to base all the polygons off a
common interface, like so:
class IPolygon
{
virtual int numVerts() = 0;
virtual Vertex3& getVert(int i) = 0;
.
.
.
};
class IndexedPoly : public IPolygon {...}
class WorkerPoly : public IPolygon {...}
void PolyOps_ClipPolygon(Plane* pPlane, IPolygon* pPoly); |
While this is a good solution in general, there are several problems with it
when you are looking at it from a performance perspective. First of all, the
addition of virtual methods adds an additional 4-bytes to the size of each
instance. Secondly, the invocation of a virtual method is slower because you
have to grab the function address from the vtable instead of calling it by
address directly.
C++ templates give you an additional approach, which if used in good measure,
can offset these costs. Keep in mind that inheritance from an abstract base
class is the preferred way of handling polymorphism, we're only interested in
optimizing areas where size and speed really matter, while still being able to
take advantage of code reuse.
Consider the following code snippet:
class WorkerPoly
{
int numVerts();
Vertex3& getVert(int i);
.
.
.
};
class IndexedPoly
{
int numVerts();
Vertex3& getVert(int i);
.
.
.
}; |
These two polygon classes, while different, do have the same method signatures.
We could take these signatures to provide a generic template function that
operates on both datatypes:
template<class T
void PolyOps_ClipPolygon(Plane* pPlane, T* pPoly); |
Note that this increases the compiled code size for each new datatype you add
that is used as the template parameter, but since you probably only have a
handful of different types of polygons at most, this is negligible. The
advantage is that now you have the code to split a polygon in one place, yet do
not have any additional runtime overhead.
|
The zip file viewer built into the Developer Toolbox made use
of the zlib library, as well as the zlibdll source additions.
|