Sign in to follow this  

Weird Template Usage (working around a lack of virtual static)

This topic is 4137 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm working on an engine redesign for Crown and Cutlass. We are working on an event system similar to what is in "Game Coding Complete". The idea is that we will allow listeners to subscribe to be notified of certain event types. When an event gets fired, anything that has subscribed to that event type will get notified. In "Game Coding Complete" the author describes a system where each event type has a string describing the type. That string is then hashed into an unsigned int for faster comparison. Hashing the string only happens once per type. The event system can also verify that there are no hash collisions. That is nice because you don't have to do string comparisions, but you still have a human readable event name for debugging. You also do not need a single event type enumeration or anything like that. So anyway, once you have the hash value, you can just look that up in a map to get a list of event listeners. In "Game Coding Complete" there are 3 classes that make up an event. An EventType class (holds the string and does the hashing), an EventData class (has the event specific data), and a general Event class. The Event class has an EventType and an EventData. For example if you want to make a "play sound" event, I guess you'd have to write a PlaySoundType that inherits from the EventType to hold the "play sound event" string, and a PlaySoundData class that inherits from the EventData to hold the file name and audio buffers or whatever. Then you'd instantiate an Event that has a pointer the the PlaySoundType and holds a PlaySoundData object. That seems a little excessive to me. You would have to take extra steps to ensure that you only have a single instance of each EventType created at a time. Doing that with singletons wouldn't be too bad, but it seems like you would have to rewrite the singleton code for every custom event type you wish to create. That's not horrible, but it seems kind of tedious. Alternatively, you could write some kind of event type manager, but that would be more work. I would rather just have a base Event class and inherit my custom event classes directly from that. My plan was to just add a static name and static hash value to the base Event class and have each sub-class override that. My idea was that each sub-class would have only a single copy of the name string, and the hashing would only have to happen once per sub-class. Unfortunately C++ does not support static virtual members/methods. While I understand the reasons for that, I do think virtual static methods would be a good fit for this problem. Based on a comment from a "Joel On Software" post, we came up with the following solution: This is the event base class and the weird template class:
#if !defined( _IEVENT_H_ )
#define _IEVENT_H_

#include <string>
#include "Util.h"

class IEvent {
public:
  virtual ~IEvent() {};
  virtual unsigned int vGetType() = 0;
  virtual void vFireEvent() = 0;
};

template<class T> class EventTmpl: public IEvent {
public:
  static unsigned int sGetType() {
    if (T::sHash == 0) {
      // CHECK THAT sName IS SET!
      T::sHash = HashString(T::sName);
      // CHECK FOR sHash COLLISIONS!
    }
    return T::sHash;
  }
  unsigned int vGetType() {
    return sGetType();
  }
};

#endif
This is an example custom event type:
// Event1.h
#if !defined( _EVENT1_H_ )
#define _EVENT1_H_

#include "IEvent.h"

class Event1: public EventTmpl<Event1> {
public:
  void vFireEvent();
  static unsigned int sHash;
  static std::string sName;
};

#endif

// Event1.cpp
#include <cstdio>
#include "Event1.h"

using namespace std;

string Event1::sName = "EVENT 1";
unsigned int Event1::sHash = 0;

void Event1::vFireEvent() {
  printf("Fire Event1\n");
}
This is another custom event class, just for the example:
// Event2.h
#if !defined( _EVENT2_H_ )
#define _EVENT2_H_

#include "IEvent.h"

// Note: I did this typedef just to try it out
class Event2;
typedef EventTmpl<Event2> Event2Type;

class Event2: public Event2Type {
public:
  void vFireEvent();
  static unsigned int sHash;
  static std::string sName;
};

#endif

// Event2.cpp
#include <cstdio>
#include "Event2.h"

using namespace std;

string Event2::sName = "EVENT TWO";
unsigned int Event2::sHash = 0;

void Event2::vFireEvent() {
  printf("Fire Event2\n");
}
Here is a main function just for a usage example. It demonstrates both how the event system would get the type of an event to send it to all the registered listeners, as well as how a listener would get the type of the custom event class it wants to register for without an actual instance of the class:
#include <cstdio>
#include <string>
#include "Event1.h"
#include "Event2.h"

using namespace std;

int main(int argc, char* argv[]) {
  IEvent* e = new Event1();
  // Event system would use e->vGetType() internally
  printf("%u\n", e->vGetType());
  // Listeners would subscribe to events using T::sGetType()
  printf("%u\n", Event1::sGetType());
  e->vFireEvent();
  delete e;

  e = new Event2();
  printf("%u\n", e->vGetType());
  printf("%u\n", Event2::sGetType());
  e->vFireEvent();
  delete e;

  return 0;
}
First off, I should say that this does work as expected in Borland C++Builder 2006, GCC 3.4, and GCC 4.1. That said, I'm not very familiar with templates. In fact I am embarassed to say that this may be the first time I have ever written a template. From an OO point of view, it seems ugly that the parent class should be explicitely aware of the child class like that. However, the parent class only exists for this one reason and you would never use it directly (you'd always use the base Event or child class). This does get us a few other things, too. It allows us to write the hashing code once and use it for all subclasses. As long as we descend from the template class, we only have to provide the class name and initialize the hash value to 0. We even get compile-time checking that we do that. In a previous revision of this idea the template class actually had the static variables, which seemed functionally the same but gave a linker error instead of a compiler error. Either way the hashing only happens once, since there is only one static var for each custom class. As I noted above, it is nice that a listener can subscribe to an event using the static sGetType, and the event system or even an event handler can figure out the type of an event using the virtual vGetType. And while the template code itself is weird, the implementation of custom event classes is fairly clean. I do have several questions about this though. First off, is there any way I can automatically initialize the static hash value to 0, so the person creating the custom event doesn't have to do that? One other thing that is a little weird is that you have to make the static variables public in the custom event class since the template class needs to use them. I guess I could declare the template class a friend, but that's just one more step you have to remember. Maybe that's not too bad. I could also go back to having the static vars in the template class and deal with the less clear linker error instead... What do you think? Is there a better way to solve this problem? Is it worth the template weirdness for the benefits? If you were looking at an engine to use for a game and you saw this going on, would you be able to understand what you needed to do to implement a custom event class? While we are working on this engine for a specific purpose, I would like to make it as easy to understand as we reasonably can. I suspect no matter what we do, we will have to document how to create custom events fairly well. I appreciate any comments you have, thanks.

Share this post


Link to post
Share on other sites
In C++, static variables and methods are accessed in the following way: type::name. In other words, you access them without requiring an instance of the class in question. If you're planning to access them like that, then you can simply override the statics in each base class:


class base
{
public:
const static std::string name;
const static int hash;
};


class derived : public base
{
public:
//the values of these two are distint from their counterparts in base
const static std::string name;
const static int hash;
};

//access them like
int currentHash = derived::hash; //or base::hash



If you wish to access them through instances of the classes involved, then you want to use regular virtual functions with local statics:


class base
{
public:
virtual const std::string& name()=0;
virtual const int hash()=0;
protected:
int hash(const std::string& in);//calculates the hash value
};

class base
{
public:
virtual const std::string& name()
{
static std::string Name = "EventName";
return Name;
}

virtual const int hash()
{
static int Hash = hash(name());
}
};



Share this post


Link to post
Share on other sites
Quote:
Original post by Nitage
If you wish to access them through instances of the classes involved, then you want to use regular virtual functions with local statics.


This sounds along the lines of what you want. To be more precise what I think you want is the monostate design pattern, ie. a class that has member functions (in your case virtual) and only static data. This way you can create as many instances of PlaySoundType as you like, they all refer to the same data (and each instance be very small, requiring only a vtable pointer).

Share this post


Link to post
Share on other sites
Thanks for the responses. I think I tend to over-engineer things. That said, either I don't understand how what you are talking about works, or you don't understand what I am going for. For example, let's say I define my base IEvent class like this:
class IEvent {
public:
virtual ~IEvent() {};
virtual unsigned int vGetType() = 0;
virtual void vFireEvent() = 0;

// Note these two lines, I'll talk about them later
static unsigned int sHash;
static std::string sName;
};

Then I define a derived event like this (and if I want to define an Event2, it's identical except for the class name):
class Event1: public IEvent {
public:
void vFireEvent();
unsigned int vGetType();

static unsigned int sGetType();
static unsigned int sHash;
static std::string sName;
};

What does that get me? Yes, I do have the virtual vGetType that will allow my event system to figure out the type to do the listener look up, but the implementation is entirely in the hands of the derived class's writer. For example, there is nothing preventing the derived class writer from just making up a type number and always returning it. Alternatively, the derived class could calculate the hash from the string every time vGetType() is called. Nitage, your example code recalculates the hash every time. Unnecessary hash calculations are one of the things I want to avoid. Only 1 hash calculation per event type should be necessary.

It is true that I also have the static sGetType that my listeners can use to register for a type of an event without having to have an actual instance of that class on hand. That is, I can still do something like "EventSystem->AddListener(Event1::sGetType(), MyListener)". That's good too. However, that relies on a convention, not something that is enforced by the compiler. There is nothing to make sure the derived event actually has such a method. A non-conventional (i.e. poorly implemented) derived class may force the listener to register for that kind of an event by creating an instance of the class and calling the vGetType(). As I expect the programmers using the engine to define many custom events, it would be nice to able to have something more concrete.

Strictly speaking, I would not say that the derived Event1's versions of sHash and sName override the base IEvent static variables, I would say they hide the base class's static variables. For example, if I have a variable "IEvent *e" and it is actually an instance of the derivived Event1, if I call "e->sName" I will actually get the IEvent::sName. While I'm not sure why you would access that directly, I really want something that behaves both as virtual and as static.

In the IEvent code, I said to note the two lines where I declare the base class's static members. I'm not sure what they do. Why even have them there? What good do they do? As I already said, I don't want to ever use the data from the base class. Having them there doesn't in any way force you to add the static members to your derived classes. I guess you could say that they remind you to do that, but at the same time you could say they are confusing since there is no indication that you should be overriding those variables...

My first example has all the benefits of this simpler approach, but it also eliminates a lot of the drawbacks. The code to do the hashing is only written once and it works for anything that derives from the template. The hash itself is guaranteed to only happen once per class. The compiler makes sure you have declared the static variables, so you are forced to used them. The drawbacks are that the template code is weird, and that you have to remember to derive from the template-generated class. The template code is only written once and then you don't have to worry about it being weird. It seems like remembering to derive from the template generated class is a lot easier than everything you have to remember with the simpler approach.

Again, maybe I'm missing something, or just trying to make it harder than it needs to be. Am I way off base here, or does the template actually solve some problems?

Share this post


Link to post
Share on other sites
OK, here's a slight variation on Nitage's 2nd example that uses a little templated code to remove the need for defining the hash() function per event.


// Provides an interface for access the name and
// hash of the event, as well as the code that does
// the actual calculation of the hash.
class IEvent
{
public:
virtual const std::string& name() = 0;
virtual const int hash() = 0;
protected:
static int hash(const std::string& in);
};

// An 'in-between' layer to automate the generation
// of the hash() function. The EVENT template parameter
// is the type of the deriving event class. Even though
// it isn't used, it ensure a unique EventBase<> exists
// for each event type, thus a unique static int in the
// hash() function exists for each event type.
template<typename EVENT>
class EventBase : public IEvent
{
public:
virtual const int hash()
{
// this is calculated only ONCE the first
// time hash() is called per instantiation
// of EventBase (ie. once per event type)
static const int hash_ = hash(name());
return hash_;
}
};

// Now in our event all we need to do is give
// it a name that will automatically be hashed
// be EventBase<Event1>::hash()
class Event1 : public EventBase<Event1>
{
public:
virtual const std::string& name()
{
static std::string name_ = "EventName";
return name_;
}
};



From here you can create as many instances of Event1 as you like and pass it around as an IEvent*, the instance will be very lightweight (containing only the vtable pointer) and will always refer to the correct name and hash for the event.

For example you can do the following:

class EventSystem
{
public:
typedef ... ListenerFunc;
void AddListener(const IEvent& event, ListenerFunc listener);
};

void func(EventSystem& eventSys)
{
//...
eventSys.AddListener(Event1(), MyListener);
//...
}

;

Share this post


Link to post
Share on other sites

This topic is 4137 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this