开发者

Ensuring there's only one of every state

I'm writing a game engine, and right now I was thinking about how I could ensure that ever state in the game (be it an entity state, a game state, etc) has only one instance. Singletons come to mind, but that seems like overkill. One other thing I thought about is nameless classes:

class EntityState
{
  public:
    virtual void foo() = 0;
};

class : public EntityState
{
  public:
    void foo() {}
} walkLeftState;

// Because it inherits from EntityState, a named class, I can also
// pass it as a parameter:
void Entity::changeState(EntityState* state) {}

But the problem is I want to add Python scripting, and I don't think Python has nameless classes. What other options are there?

Edit: Why I need only one of every state.

I need a away to identify what state an entity has开发者_运维知识库. I could do it with magic values (i.e. an ID string), but that's a terribly ugly solution, imo. I'd much rather do it by comparing pointers, which I'm ensured to always be unique. But I can't identify by pointers unless there's a single instance of every state...

Edit 2: A solution...

I went for function objects in the end. Seeing as the State classes would only contain a function and nothing else, it didn't really make sense having the class in the first place, now that I think about it. In the end, I think I'll go for something like this:

typedef void (*EntityStateFunc)(Entity* entity, unsigned long currentTime);

namespace entitystate
{
    void walkLeft(Entity* entity, unsigned long currentTime);
    void stand(Entity* entity, unsigned long currentTime);
}

This should pretty much solve everything: no singletons, no complaints that it might make testing harder, it's simple... Only possible disadvantage is that I'll pretty much have my hands tied if a state ever needs to be more than a function; can anyone think of a scenario where a state needs to be something more complex than this?


Just create one of every state.

There is no reason to make your code so complicated by attempting to enforce this.

Do you write a function like this:

int foo() {
   int x = 5;
   x++;

   return x;
}

And then go, oh my god, I only need one integer variable inside this function... I must enforce this? No.


You could avoid creating a hard constraint, and instead ensure that you're notified if and when you ever create more than you expected:

template <typename T>
struct expected_unique {
    static int &getcount() {
        static int count = 0;
        return count;
    }
    static void object_created() {
        int &lcount = getcount();
        ++lcount;
        if (lcount > 1) {
            std::err << "More than one " << typeid(T).name() << " " << lcount << "\n";
        }
    }
    expected_unique() {
        object_created();
    }
    expected_unique(const expected_unique&) {
        object_created();
    }
    // optionally, if you only want to check no more than one at a time 
    // rather than no more than one ever.
    ~expected_unique() {
        --getcount();
    }
};

class WalkLeftState : public EntityState, private expected_unique<WalkLeftState> {
};

Obviously it's not thread-safe, you could make it so with a lock or with atomic int operations.

If there really is only one function, another alternative is:

class EntityState
{
  void (*foo_func)();
  public:
    EntityState(void(*f)()) : foo_func(f) {}
    bool operator==(const EntityState &rhs) {
        return foo_func == rhs.foo_func;
    }
    bool operator!=(const EntityState &rhs) {
        return !(*this == rhs);
    }
    void foo() { foo_func(); }
};

void walk_left_foo() {
}

EntityState walkLeftState(walk_left_foo);

Now, it doesn't matter whether or not there are multiple instances of EntityState using the same function, because comparison is performed according to whether the two states involved execute the same routine. So just switch your existing pointer comparisons to object comparisons.

However, if there's more than one virtual function in EntityState in real life, then this would be pretty unwieldy.


Singletons probably are overkill here. It's laudable to be defensive in your programming, and to prevent misuse, but in this case, the simplest solution is just to define each of the state classes in an unnamed namespace in the source file. You not going to create multiple instances in this one file, and no one else can even name them, much less define an instance of one. (You can also leave them unnamed, but that means no user defined destructor.)


I need a away to identify what state an entity has.

Why? To be able to switch on the state? That's exactly what the State pattern should prevent you from doing in the first place. It replaces "switching on states" with polymorphism. That is, instead of this:

switch (state.getState())
{
    case WALK_LEFT:
        --x;
        break;

    case WALK_RIGHT:
        ++x;
        break;

    case WALK_UP:
        --y;
        break;

    case WALK_DOWN:
        ++y;
        break;
}

you just say:

state.step();

and let the concrete step member functions do the right thing.


As I "hinted at" in the comments, enforcing a "only one instance may exist" constraint is nearly always the wrong thing to do. Actually, I'm willing to go out on a limb and say that it is always the wrong thing to do.

Instead, make it impossible to accidentally create new instances, but allow the programmer (you) to do so when you really want to.

In C++, there are two common ways in which instances might "accidentally" get created:

EntityState st = otherState;

EntityState st2;

Copy constructors are probably the #1 offender. It's easy to forget the &, so suddenly instead of creating a reference to the object, you create a copy. So prevent this by making the class noncopyable.

The default constructor is a less serious issue, but you might, for example, create a class member of type EntityState, and forget to initialize it. And voilá, the default constructor gives you a new instance of the class.

So prevent that too. Declare a constructor taking one or more parameters (which you don't accidentally call), ensure that no default constructor exists, and that the copy constructor is private.

Then you have to consciously think about it and want to create an instance before it happens. And so, as long as the programmer is sane, you'll only have one instance whenever you want just one instance to exist.

0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜