Is there a better way to handle assigning identities to classes within a hierarchy for runtime use?

StackOverflow https://stackoverflow.com/questions/12573163

  •  03-07-2021
  •  | 
  •  

Domanda

I'm trying to make a sanely-usable implementation of my events system. In order to identify event types, I have what I call a "type path,' which identifies the path to an event type through the hierarchy. This way, I can handle, for example, all InputEvents at one place whether they're key presses, mouse input, or whatever else. A sticky issue, though, has been giving event types their identities. What I've most recently done is do this by having each instance retrieve a leaf identity from a static member function of the Event class, which serves simply as an interface other than performing this function. However, the simplest way to ensure that each type has exactly one identity within this structure seemed to be to use maps based on type paths (up to but excluding the leaf identity/identifier) and typeid().hash_code().

Specifically, what I want to have is a system to which events can be added easily without having to look up a bunch of information or perform a lot of silly boilerplate crap. Considering this (and possibly things I'm not realizing I should want?),

  1. Is this design flawed in any obvious ways?
  2. Is there a Better™ way than to use typeid()? I've read a bit about it and it seems to be considered something that, depending on the person whose opinion is being asked, should either never be used or be used almost never. As it's possible that I'm just rusty or being stupid, I'd like to know if anyone knows of a solution that is, if nothing else, less uncertain (apparently some implementations of typeid() are pretty bad, though I don't know which or if it's bad enough to seriously matter).

Fairly simple example of what I have now:

#include <iostream>
#include <vector>
#include <typeinfo>
#include <map>

void spew(std::vector<unsigned int> vect) { for (unsigned int i=0;i<vect.size();++i) std::cout << vect.at(i) << ","; std::cout << std::endl; }

class Foo
{
public:
    Foo() {}
    virtual ~Foo() {}
    static unsigned int getSubtype(std::vector<unsigned int> typePath, Foo *evt)
    {
        static std::map<std::vector<unsigned int>, std::map<std::size_t, unsigned int> > typeMap;
        std::size_t typehash = typeid(*evt).hash_code();
        if (typeMap.find(typePath) == typeMap.end())
        {
            unsigned int val = typeMap[typePath].size();
            typeMap[typePath][typehash] = val;
            return val;
        }
        else
        {
            if (typeMap[typePath].find(typehash) == typeMap[typePath].end())
            {
                unsigned int val = typeMap[typePath].size();
                typeMap[typePath][typehash] = val;
                return val;
            }
            return typeMap[typePath][typehash];
        }
    }
    virtual void test() { std::cout << "Foo" << std::endl; }
protected:
    std::vector<unsigned int> m_typePath;
};

class Bar : public Foo
{
public:
    Bar()
    {
        m_typePath.push_back(Foo::getSubtype(m_typePath, this));
        test();
    }
    virtual ~Bar() {}
    virtual void test() { std::cout << "Bar: "; spew(m_typePath);}
};

class Baz : public Foo
{
public:
    Baz()
    {
        m_typePath.push_back(Foo::getSubtype(m_typePath, this));
        test();
    }
    virtual ~Baz() {}
    virtual void test() { std::cout << "Baz: "; spew(m_typePath);}
};

class Qux : public Baz
{
public:
    Qux()
    {
        m_typePath.push_back(Foo::getSubtype(m_typePath, this));
        test();
    }
    virtual ~Qux() {}
    virtual void test() { std::cout << "Qux: "; spew(m_typePath);}
};

int main()
{
    Foo foo0;
std::cout << "----" << std::endl;
    Bar bar0;
std::cout << "----" << std::endl;
    Baz baz0;
std::cout << "----" << std::endl;
    Qux qux0;
}

Output:

----
Bar: 0,
----
Baz: 1,
----
Baz: 1,
Qux: 1,0,

This and other tests exhibit the desired behavior, to be clear.
Edit: Previous title didn't really match what I mean to ask. Possibly relevant notes: This is meant for part of a library, and a highly parallel one at that. I've omitted code relevant to concurrency for simplicity of representing the design, but it may be that such information would be useful for design purposes as well. Also note that I'm still only asking for help with creating/assigning type identifiers; I mention these because some designs may not be applicable given their implied constraints.

Win edit: Well, I have an implementation that's ridiculously fast and does exactly what I need. With a few derived classes, I can instantiate ten million per ~thread(I added in TBB for some other tests; it may or may not use exactly eight threads however it pleases) spread across the derived classes, each having two or more elements in its path, in typically well under .02s. Original implementation managed about four or five seconds depending on containers and such and was just silly. Result (enough to get the idea, anyway):

template<typename T> class EventID
{
public:
    static const std::size_t typeID;
};

template<typename T> const std::size_t EventID<T>::typeID = typeid(T).hash_code();

class Foo
{
public:
    Foo()
    {
        m_typePath.push_back(EventID<Foo>::typeID);
    }
protected:
    neolib::vecarray<std::size_t, 100, neolib::nocheck> m_typePath;
};

class Bar : public Foo
{
public:
    Bar()
    {
        m_typePath.push_back(EventID<Bar>::typeID);
    }
};
È stato utile?

Soluzione 2

See final edit to original post for solution.

Altri suggerimenti

I would rely on the class hierarchy that the compiler maintains, plus a list of type codes:

typedef enum { EVENT, INPUT, MOUSE, MOUSEDOWN, MOUSEUP, MOUSEMOVE, KEYBOARD, KEYDOWN, KEYUP } EventType;

typedef std::vector<EventType> ETVector;

class Event
{
public:
    virtual void appendType(ETVector& v) { v.push_back(EVENT); }
};

class InputEvent : public Event
{
public:
    virtual void appendType(ETVector& v) { v.push_back(INPUT); Event::appendType(v); }
};

class MouseEvent : public InputEvent
{
public:
    virtual void appendType(ETVector& v) { v.push_back(MOUSE); InputEvent::appendType(v); }
};

class MouseDownEvent : public MouseEvent
{
public:
    virtual void appendType(ETVector& v) { v.push_back(MOUSEDOWN); MouseEvent::appendType(v); }
};

class MouseUpEvent : public MouseEvent
{
public:
    virtual void appendType(ETVector& v) { v.push_back(MOUSEUP); MouseEvent::appendType(v); }
};

class MouseMoveEvent : public MouseEvent
// . . .

class KeyboardEvent : public InputEvent
// . . .

class KeyDownEvent : public KeyboardEvent
// . . .

class KeyUpEvent : public KeyboardEvent
// . . .

Then to do your test, you would have something like this:

KeyUpEvent kue;

EventTypeVector type_path;

kue.appendType(type_path);

for (EventTypeVector::const_iterator i = type_path.begin(); i != type_path.end(); i++)
{
    cout << *i << endl;
}

The vector stores your type path. There is no runtime storage cost, and no static variables. It might be possible to use typeid instead of a manually maintained enum, but with an enum everything is under your control, and you can easily avoid conflicts with other types in your program. Alternatively, it might be possible to imbue a class with the appendType() method by means of a template, to reduce the code even further.

It is a little tedious to have to explicitly name the parent class in appendType(), but I know of no other way in C++, thanks to multiple inheritance. In Java I could have used super, though reflection would probably be a better approach.

Is this simpler than what you have, or am I missing something? Good luck.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top