根据类型标志将基类转换为派生类

时间:2014-07-06 20:52:35

标签: c++ oop

我一直在为我的游戏引擎编写一个事件类,我遇到了以下问题:

在给定类型标志的情况下,将基类对象转换为派生类对象是一种很好的编程设计吗?

让我举几个简单的例子。 我有这个enum持有事件的类别:

enum DisplayEventType
{
    DET_UNSET,
    DET_WINDOW_EVENT,
    DET_KEYBOARD_EVENT,
    DET_MOUSE_EVENT,
    DET_JOYSTICK_EVENT
};

这是所有事件的基类:

typedef std::unique_ptr<DisplayEventData> DisplayEventDataMP;

class DisplayEvent
{
    private:

        // The type of the display event
        DisplayEventType mType;

        // The data of the event
        DisplayEventDataMP mEventData;

    public:

        // Constructor
        DisplayEvent(DisplayEventType type = DisplayEventType::DET_UNSET, DisplayEventDataMP eventData = std::unique_ptr<DisplayEventData>((DisplayEventData*) 0));

        // Retrieves the event type of the current event
        DisplayEventType GetType() const;

        // Sets the event type of the current event
        void SetType(DisplayEventType type);

        // Retrieves a raw pointer to the event data of the current event
        DisplayEventData* GetEventData() const;

        // Sets the event data of the current event
        void SetEventData(DisplayEventDataMP eventData);
};

这是事件数据的基类:

class DisplayEventData
{
    public:

        // Virtual destructor, for the deallocation of the interface implementors to work
        virtual ~DisplayEventData();
};

这些是派生类

class WindowEventData : public DisplayEventData
{
    // Window event data specific data and methods
};

class KeyboardEventData : public DisplayEventData
{
    // Keyboard event data specific data and methods
};

class MouseEventData : public DisplayEventData
{
    // Mouse event data specific data and methods
};

class JoystickEventData : public DisplayEventData
{
    // Joystick event specific data and methods
};

一些基本用法是:

// Poll for next event in the queue
DisplayEvent ev;
display->PollEvent(ev); // Fills ev with the needed data

if (ev.GetType() == DisplayEventType::DET_KEYBOARD_EVENT)
{
    KeyboardEventData* kev = static_cast<KeyboardEventData>(ev.GetEventData());
    // Do stuff
}

上述设计是否可行/正确?如果不是,在面向对象甚至通用编程方面会有什么更好的替代方案?

1 个答案:

答案 0 :(得分:0)

我不相信事件系统有一个'正确'的设计,特别是在游戏引擎中。我最近为自己的游戏引擎实现了一个事件系统,我将在下面分享实现细节。它可能是也可能不是最适合您的问题,但它应该在您的头脑中产生一些想法用于替代设计。

注意:我的事件系统尚未完全优化。一些简单的优化包括散列字符串和/或使用无序映射而不是映射。

关键设计要点:

  • 我的事件系统中的核心设计习惯是拥有一个非模板基类和一个继承自非模板基类的模板子类
  • 所有事件函数必须符合TypedEventSubscriber类中定义的特定函数签名
  • 可以使用NamedProperties,NamedProperty和TypedNamedProperties类传递任意数量的参数和参数,NamedProperty和TypedNamedProperties类遵循第一个项目符号中提到的项目符号
  • 成员函数指针用于回调,因此特定对象可以接收事件
  • 事件系统不需要继承;任何人都可以通过与EventManager单身人士交谈来注册和解雇事件
  • 动态强制转换用于从NamedProperties类中的回调函数中检索参数。虽然Dynamic Cast并不总是最佳实践,但使用它可以让我们通过回调函数传递0..N可能的参数。

非模板NamedProperty类的示例(非常简单)

class NamedProperty {
public:
    explicit NamedProperty();
    virtual ~NamedProperty();

};

模板化TypeNamedProperty类的示例

template < typename T_PropertyDataType >
class TypedNamedProperty : public NamedProperty {
public:
    friend class NamedProperties;

    explicit TypedNamedProperty<T_PropertyDataType>() {}
    virtual ~TypedNamedProperty() {}

    // Inline Mutators
    inline void TypedNamedProperty<T_PropertyDataType>::setData( const T_PropertyDataType & data ) {
        m_data = data;
    }


    inline void TypedNamedProperty<T_PropertyDataType>::getData( T_PropertyDataType & data ) {
        data = m_data;
    }

private:
    T_PropertyDataType      m_data;

};

NamedProperties类的示例,它是NamedProperty参数的容器。它返回一个枚举,指示属性检索的状态。因为我正在处理任何类型的0..N可能的params,所以getProperty是一个模板函数,它将为所提供的类型使用动态强制转换。如果动态转换失败(Failed dynamic casts return nullptr),它将设置适当的枚举值并返回。如果成功,它将返回正确的参数。

typedef enum {

    PROPERTY_GET_SUCCESS,
    PROPERTY_GET_FAILED_NO_SUCH_PROPERTY,
    PROPERTY_GET_FAILED_WRONG_TYPE,
    PROPERTY_GET_FAILED_NULLPTR,
} PropertyResult;

class NamedProperties {
public:

    ~NamedProperties();

    // Mutators
    template <typename T_PropertyDataType>
    inline PropertyResult getProperty( const std::string & propertyName, T_PropertyDataType & valueOut ) {

        PropertyResult result;
        std::map<std::string, NamedProperty *>::iterator itProp;
        itProp = m_namedProperties.find( propertyName );
        if ( itProp != m_namedProperties.end() ) {
            // property found in the map... Need to cast it to check if the type is correct
            NamedProperty * retrievedResult = itProp->second;
            if ( retrievedResult == nullptr ) {
                result = PROPERTY_GET_FAILED_NULLPTR;
                return result;
            } 

            TypedNamedProperty<T_PropertyDataType> * propertyWithType = dynamic_cast<TypedNamedProperty<T_PropertyDataType> *>( retrievedResult );
            if ( propertyWithType != nullptr ) {
                propertyWithType->getData( valueOut );
                result = PROPERTY_GET_SUCCESS;
            } else {
                result = PROPERTY_GET_FAILED_WRONG_TYPE;
            }
        } else {
            // No such property exists in the map
            result = PROPERTY_GET_FAILED_NO_SUCH_PROPERTY;

        }
        return result;
    } // end getProperty


    template <typename T_PropertyDataType>
    inline void setProperty( const std::string & propertyName, const T_PropertyDataType & propertyValue ) {

        TypedNamedProperty<T_PropertyDataType> * typedProperty = new TypedNamedProperty<T_PropertyDataType>;
        typedProperty->setData( propertyValue );
        m_namedProperties.insert( std::pair< std::string, TypedNamedProperty<T_PropertyDataType>*>( propertyName, typedProperty ) );
    }

protected:
    std::map<std::string, NamedProperty* >          m_namedProperties;
};

非模板EventSubscriberBase类的示例,它是存储在EventSystemManager单例中的EventSystemSubscribers数据结构中的基类。注意虚拟的callCallbackFunction是纯虚拟的。

class EventSubscriberBase {
public:
    virtual ~EventSubscriberBase() {}
    EventSubscriberBase() {}

    virtual void callCallbackFunction( NamedProperties & params ) = 0;

    virtual void * getSubscriberObjectPointer() = 0;
};

模板化TypedEventSubscriber类的示例

#include "EventSubscriberBase.hpp"

template< typename T_SubscriberType >
class TypedEventSubscriber : public EventSubscriberBase {

typedef void ( T_SubscriberType::*ObjectMemberFunctionType )( NamedProperties & );

public:
    virtual ~TypedEventSubscriber<T_SubscriberType>() {}
    explicit TypedEventSubscriber<T_SubscriberType>() {
        m_subscriberObject = nullptr;
    }

    virtual void callCallbackFunction( NamedProperties & params ) {
        (m_subscriberObject->*m_callbackFunction)(params);
    }


    void setCallbackFunction( ObjectMemberFunctionType callbackFunction ) {
        m_callbackFunction = callbackFunction;
    }


    void setSubscriber( T_SubscriberType * subscriber ) {
        assert( subscriber != nullptr );
        m_subscriberObject = subscriber;
    }


    T_SubscriberType * getSubscriber() {
        return m_subscriberObject;
    }

    virtual void * getSubscriberObjectPointer() {
        return m_subscriberObject;
    }

private:

    T_SubscriberType *          m_subscriberObject;
    ObjectMemberFunctionType    m_callbackFunction;
};

请注意,成员函数指针有一个typedef。这就是使用模板类的原因。这允许任何类通过提供模板参数来注册其类型的回调函数。以下是游戏代码如何注册活动的示例...

在.hpp标题中

void saveFileCallbackFunc( NamedProperties& namedProps );

在.cpp文件中

// Call this function in the CTOR or appropriate initialize function
RegisterEventCallbackForObject( "EventNameForCallbackHere", this, &FireAntGame::saveFileCallbackFunc );

// The callback function definition
void FireAntGame::saveFileCallbackFunc( NamedProperties& namedProps ) {

    unsigned int fileBufferSize = 0;
    char* fileBuffer = nullptr;

    namedProps.getProperty( SF_FILE_BUFFER_STRING, fileBuffer );
    namedProps.getProperty( SF_FILE_BUFFER_SIZE_STRING, fileBufferSize );
}

用于注册和触发事件的EventSystem单例类函数实现的示例。要注意的重要细节是单例存储EntitySubscriberBase类指针的映射而不是子模板类TypedEntitySubscriber。

template< typename T_RegisteringObjectType, typename T_ObjectMethodType >
    void registerEvent( const std::string & eventName, T_RegisteringObjectType * classObject, T_ObjectMethodType callbackFunction ) {
        assert( classObject != nullptr );
        TypedEventSubscriber< T_RegisteringObjectType > * eventSubscriber = new TypedEventSubscriber< T_RegisteringObjectType >;
        eventSubscriber->setSubscriber( classObject );
        eventSubscriber->setCallbackFunction( callbackFunction );

        std::map< std::string, std::vector<EventSubscriberBase*>>::iterator it;
        it = m_eventSubscribers.find( eventName );
        if ( it != m_eventSubscribers.end() ) {

            std::vector<EventSubscriberBase*>& eventSubVec = it->second;
            eventSubVec.push_back( eventSubscriber );
        } else {

            // Event did not previously exist
            std::vector<EventSubscriberBase*> eventSubVec;
            eventSubVec.push_back( eventSubscriber );
            std::pair<std::string,std::vector<EventSubscriberBase*>> elementToInsert;
            elementToInsert.first = eventName;
            elementToInsert.second = eventSubVec;
            m_eventSubscribers.insert(elementToInsert);
        }
    }



void fireEvent( const std::string & eventName, NamedProperties & params ) {

        std::map<std::string,std::vector<EventSubscriberBase*>>::iterator itFind;
        itFind = m_eventSubscribers.find( eventName );
        if ( itFind != m_eventSubscribers.end() ) {

            // Someone has registered for this event
            std::vector<EventSubscriberBase*> & subscribersVector = itFind->second;

            for ( size_t i = 0; i < subscribersVector.size(); ++i ) {
                EventSubscriberBase * eventSubscriber = subscribersVector[i];
                eventSubscriber->callCallbackFunction( params );
            }
        }
    }

这种设计的优点在于任何对象都可以在没有继承的情况下注册事件。 这也意味着不必为所涉及的每种可能的类类型创建枚举。它还允许通过NamedProperties类在回调函数中传递0..N参数。如果在从NamedProperties类请求属性时不存在属性,则返回默认值(如果您愿意,可以由用户指定)。

最终,关键的用处是使用带有模板子类的非模板基类。没有这种设计习语,它就会崩溃。这允许我们的数据结构利用基类指针的多态性,该指针可以调用任何成员函数类型的回调函数,或者通过任何类型的NamedProperties传入参数!在我看来,这为事件系统提供了最大的灵活性。

对不起,很长的帖子!希望这为实现事件系统提供了一个想法。