std::tr1::function::target;和协/逆变

发布于 2024-09-09 01:50:37 字数 7372 浏览 5 评论 0原文

由于我喜欢使用 C# 和 C++ 进行编程,因此我即将实现一个类似 C# 的事件系统,作为我计划的 C++ SFML-GUI 的坚实基础。

这只是我的代码的摘录,我希望这能澄清我的概念:

// Event.h
// STL headers:
#include <functional>
#include <type_traits>
#include <iostream>
// boost headers:
#include <boost/signals/trackable.hpp>
#include <boost/signal.hpp>

namespace Utils
{
    namespace Gui
    {
        #define IMPLEMENTS_EVENT(EVENTNAME, EVENTARGS) public: \
            Utils::Gui::IEvent<EVENTARGS>& EVENTNAME() { return m_on##EVENTNAME; } \
        protected: \
            virtual void On##EVENTNAME(EVENTARGS& e) { m_on##EVENTNAME(this, e); } \
        private: \
            Utils::Gui::Event<EVENTARGS> m_on##EVENTNAME;


        #define MAKE_EVENTFIRING_CLASS(EVENTNAME, EVENTARGS) class Fires##EVENTNAME##Event \
        { \
            IMPLEMENTS_EVENT(EVENTNAME, EVENTARGS); \
        };


        class EventArgs
        {
        public:
            static EventArgs Empty;
        };

        EventArgs EventArgs::Empty = EventArgs();

        template<class TEventArgs>
        class EventHandler : public std::function<void (void*, TEventArgs&)>
        {
            static_assert(std::is_base_of<EventArgs, TEventArgs>::value, 
                "EventHandler must be instantiated with a TEventArgs template paramater type deriving from EventArgs.");
        public:
            typedef void Signature(void*, TEventArgs&);
            typedef void (*HandlerPtr)(void*, TEventArgs&);

            EventHandler() : std::function<Signature>() { }

            template<class TContravariantEventArgs>
            EventHandler(const EventHandler<TContravariantEventArgs>& rhs)
                : std::function<Signature>(reinterpret_cast<HandlerPtr>(*rhs.target<EventHandler<TContravariantEventArgs>::HandlerPtr>())) 
            {
                static_assert(std::is_base_of<TContravariantEventArgs, TEventArgs>::value,
                    "The eventHandler instance to copy does not suffice the rules of contravariance.");
            }

            template<class F>
            EventHandler(F f) : std::function<Signature>(f) { }

            template<class F, class Allocator>
            EventHandler(F f, Allocator alloc) : std::function<Signature>(f, alloc) { }
        };

        template<class TEventArgs>
        class IEvent
        {
        public:
            typedef boost::signal<void (void*, TEventArgs&)> SignalType;

            void operator+= (const EventHandler<TEventArgs>& eventHandler)
            {
                Subscribe(eventHandler);
            }

            void operator-= (const EventHandler<TEventArgs>& eventHandler)
            {
                Unsubscribe(eventHandler);
            }

            virtual void Subscribe(const EventHandler<TEventArgs>& eventHandler) = 0;

            virtual void Subscribe(const EventHandler<TEventArgs>& eventHandler, int group) = 0;

            virtual void Unsubscribe(const EventHandler<TEventArgs>& eventHandler) = 0;
        };

        template<class TEventArgs>
        class Event : public IEvent<TEventArgs>
        {
        public:
            virtual void Subscribe(const EventHandler<TEventArgs>& eventHandler)
            {
                m_signal.connect(*eventHandler.target<EventHandler<TEventArgs>::HandlerPtr>());
            }

            virtual void Subscribe(const EventHandler<TEventArgs>& eventHandler, int group)
            {
                m_signal.connect(group, *eventHandler.target<EventHandler<TEventArgs>::HandlerPtr>());
            }

            virtual void Unsubscribe(const EventHandler<TEventArgs>& eventHandler)
            {
                m_signal.disconnect(*eventHandler.target<EventHandler<TEventArgs>::HandlerPtr>());
            }

            void operator() (void* sender, TEventArgs& e)
            {
                m_signal(sender, e);
            }

        private:
            SignalType m_signal;
        };

        class IEventListener : public boost::signals::trackable
        {
        };
    };
};

如您所见,我使用 boost::signal 作为我的实际事件系统,但我用 IEvent 接口(实际上是一个抽象类)封装它) 以防止事件侦听器通过 operator() 触发事件。

为了方便起见,我重载了加赋值和减赋值运算符。如果我现在从 IEventListener 派生事件侦听类,我就可以编写代码,而无需担心信号中的悬空函数指针。

到目前为止,我正在测试我的结果,但我在 std::tr1::function::target() 方面遇到了麻烦:

class BaseEventArgs : public Utils::Gui::EventArgs
{
};

class DerivedEventArgs : public BaseEventArgs
{
};

void Event_BaseEventRaised(void* sender, BaseEventArgs& e)
{
    std::cout << "Event_BaseEventRaised called";
}

void Event_DerivedEventRaised(void* sender, DerivedEventArgs& e)
{
   std::cout << "Event_DerivedEventRaised called";
}

int main()
{
    using namespace Utils::Gui;
    typedef EventHandler<BaseEventArgs>::HandlerPtr pfnBaseEventHandler;
    typedef EventHandler<DerivedEventArgs>::HandlerPtr pfnNewEventHandler;

    // BaseEventHandler with a function taking a BaseEventArgs
    EventHandler<BaseEventArgs> baseEventHandler(Event_BaseEventRaised);
    // DerivedEventHandler with a function taking a DerivedEventArgs
    EventHandler<DerivedEventArgs> newEventHandler(Event_DerivedEventRaised);
    // DerivedEventHandler with a function taking a BaseEventArgs -> Covariance
    EventHandler<DerivedEventArgs> covariantBaseEventHandler(Event_BaseEventRaised);

    const pfnBaseEventHandler* pBaseFunc = baseEventHandler.target<pfnBaseEventHandler>();
    std::cout << "baseEventHandler function pointer is " << ((pBaseFunc != nullptr) ? "valid" : "invalid") << std::endl;

    const pfnNewEventHandler* pNewFunc = newEventHandler.target<pfnNewEventHandler>();
    std::cout << "baseEventHandler function pointer is " << ((pNewFunc != nullptr) ? "valid" : "invalid") << std::endl;

    // Here is the error, covariantBaseEventHandler actually stores a pfnBaseEventHandler:
    pNewFunc = covariantBaseEventHandler.target<pfnNewEventHandler>();
    std::cout << "covariantBaseEventHandler as pfnNewEventHandler function pointer is " << ((pNewFunc != nullptr) ? "valid" : "invalid") << std::endl;

    // This works as expected, but template forces compile-time knowledge of the function pointer type
    pBaseFunc = covariantBaseEventHandler.target<pfnBaseEventHandler>();
    std::cout << "covariantBaseEventHandler as pfnBaseEventHandler function pointer is " << ((pBaseFunc != nullptr) ? "valid" : "invalid") << std::endl;

    return EXIT_SUCCESS;
}

EventHandler::target仅当 TFuncPtr 与 Functor 中存储的类型完全相同时,无论协方差如何,;() 方法才会返回有效指针。 由于 RTTI 检查,它禁止将指针作为标准弱类型 C 函数指针来访问,这在这种情况下有点烦人。

EventHandler 的类型为 DerivedEventArgs,但仍然指向 pfnBaseEventHandler 函数,即使该函数通过构造函数运行也是如此。

这意味着 std::tr1::function 本身“支持”逆变,但如果我不知道其类型,我找不到一种简单地从 std::tr1::funcion 对象中获取函数指针的方法在编译时,这是模板参数所必需的。

在这种情况下,我希望他们添加一个简单的 get() 方法,就像为 RAII 指针类型所做的那样。

由于我对编程很陌生,我想知道是否有办法解决这个问题,最好是在编译时通过模板(我认为这是唯一的方法)。

Since I love progamming in both C# and C++, I'm about to implementing a C#-like event system as a solid base for my planned C++ SFML-GUI.

This is only an excerpt of my code and I hope this clarifies my concept:

// Event.h
// STL headers:
#include <functional>
#include <type_traits>
#include <iostream>
// boost headers:
#include <boost/signals/trackable.hpp>
#include <boost/signal.hpp>

namespace Utils
{
    namespace Gui
    {
        #define IMPLEMENTS_EVENT(EVENTNAME, EVENTARGS) public: \
            Utils::Gui::IEvent<EVENTARGS>& EVENTNAME() { return m_on##EVENTNAME; } \
        protected: \
            virtual void On##EVENTNAME(EVENTARGS& e) { m_on##EVENTNAME(this, e); } \
        private: \
            Utils::Gui::Event<EVENTARGS> m_on##EVENTNAME;


        #define MAKE_EVENTFIRING_CLASS(EVENTNAME, EVENTARGS) class Fires##EVENTNAME##Event \
        { \
            IMPLEMENTS_EVENT(EVENTNAME, EVENTARGS); \
        };


        class EventArgs
        {
        public:
            static EventArgs Empty;
        };

        EventArgs EventArgs::Empty = EventArgs();

        template<class TEventArgs>
        class EventHandler : public std::function<void (void*, TEventArgs&)>
        {
            static_assert(std::is_base_of<EventArgs, TEventArgs>::value, 
                "EventHandler must be instantiated with a TEventArgs template paramater type deriving from EventArgs.");
        public:
            typedef void Signature(void*, TEventArgs&);
            typedef void (*HandlerPtr)(void*, TEventArgs&);

            EventHandler() : std::function<Signature>() { }

            template<class TContravariantEventArgs>
            EventHandler(const EventHandler<TContravariantEventArgs>& rhs)
                : std::function<Signature>(reinterpret_cast<HandlerPtr>(*rhs.target<EventHandler<TContravariantEventArgs>::HandlerPtr>())) 
            {
                static_assert(std::is_base_of<TContravariantEventArgs, TEventArgs>::value,
                    "The eventHandler instance to copy does not suffice the rules of contravariance.");
            }

            template<class F>
            EventHandler(F f) : std::function<Signature>(f) { }

            template<class F, class Allocator>
            EventHandler(F f, Allocator alloc) : std::function<Signature>(f, alloc) { }
        };

        template<class TEventArgs>
        class IEvent
        {
        public:
            typedef boost::signal<void (void*, TEventArgs&)> SignalType;

            void operator+= (const EventHandler<TEventArgs>& eventHandler)
            {
                Subscribe(eventHandler);
            }

            void operator-= (const EventHandler<TEventArgs>& eventHandler)
            {
                Unsubscribe(eventHandler);
            }

            virtual void Subscribe(const EventHandler<TEventArgs>& eventHandler) = 0;

            virtual void Subscribe(const EventHandler<TEventArgs>& eventHandler, int group) = 0;

            virtual void Unsubscribe(const EventHandler<TEventArgs>& eventHandler) = 0;
        };

        template<class TEventArgs>
        class Event : public IEvent<TEventArgs>
        {
        public:
            virtual void Subscribe(const EventHandler<TEventArgs>& eventHandler)
            {
                m_signal.connect(*eventHandler.target<EventHandler<TEventArgs>::HandlerPtr>());
            }

            virtual void Subscribe(const EventHandler<TEventArgs>& eventHandler, int group)
            {
                m_signal.connect(group, *eventHandler.target<EventHandler<TEventArgs>::HandlerPtr>());
            }

            virtual void Unsubscribe(const EventHandler<TEventArgs>& eventHandler)
            {
                m_signal.disconnect(*eventHandler.target<EventHandler<TEventArgs>::HandlerPtr>());
            }

            void operator() (void* sender, TEventArgs& e)
            {
                m_signal(sender, e);
            }

        private:
            SignalType m_signal;
        };

        class IEventListener : public boost::signals::trackable
        {
        };
    };
};

As you can see, I'm using boost::signal as my actual event system, but I encapsulate it with the IEvent interface (which is actually an abstract class) to prevent event listeners to fire the event via operator().

For convenience I overloaded the add-assignment and subtract-assignment operators. If I do now derive my event listening classes from IEventListener, I am able to write code without needing to worry about dangling function pointer in the signal.

So far I'm testing my results, but I have trouble with std::tr1::function::target<TFuncPtr>():

class BaseEventArgs : public Utils::Gui::EventArgs
{
};

class DerivedEventArgs : public BaseEventArgs
{
};

void Event_BaseEventRaised(void* sender, BaseEventArgs& e)
{
    std::cout << "Event_BaseEventRaised called";
}

void Event_DerivedEventRaised(void* sender, DerivedEventArgs& e)
{
   std::cout << "Event_DerivedEventRaised called";
}

int main()
{
    using namespace Utils::Gui;
    typedef EventHandler<BaseEventArgs>::HandlerPtr pfnBaseEventHandler;
    typedef EventHandler<DerivedEventArgs>::HandlerPtr pfnNewEventHandler;

    // BaseEventHandler with a function taking a BaseEventArgs
    EventHandler<BaseEventArgs> baseEventHandler(Event_BaseEventRaised);
    // DerivedEventHandler with a function taking a DerivedEventArgs
    EventHandler<DerivedEventArgs> newEventHandler(Event_DerivedEventRaised);
    // DerivedEventHandler with a function taking a BaseEventArgs -> Covariance
    EventHandler<DerivedEventArgs> covariantBaseEventHandler(Event_BaseEventRaised);

    const pfnBaseEventHandler* pBaseFunc = baseEventHandler.target<pfnBaseEventHandler>();
    std::cout << "baseEventHandler function pointer is " << ((pBaseFunc != nullptr) ? "valid" : "invalid") << std::endl;

    const pfnNewEventHandler* pNewFunc = newEventHandler.target<pfnNewEventHandler>();
    std::cout << "baseEventHandler function pointer is " << ((pNewFunc != nullptr) ? "valid" : "invalid") << std::endl;

    // Here is the error, covariantBaseEventHandler actually stores a pfnBaseEventHandler:
    pNewFunc = covariantBaseEventHandler.target<pfnNewEventHandler>();
    std::cout << "covariantBaseEventHandler as pfnNewEventHandler function pointer is " << ((pNewFunc != nullptr) ? "valid" : "invalid") << std::endl;

    // This works as expected, but template forces compile-time knowledge of the function pointer type
    pBaseFunc = covariantBaseEventHandler.target<pfnBaseEventHandler>();
    std::cout << "covariantBaseEventHandler as pfnBaseEventHandler function pointer is " << ((pBaseFunc != nullptr) ? "valid" : "invalid") << std::endl;

    return EXIT_SUCCESS;
}

The EventHandler<TEventArgs>::target<TFuncPtr>() method will only return a valid pointer if TFuncPtr is the exact same type as stored in the Functor, regardless of covariance.
Because of the RTTI check, it prohibits to access the pointer as a standard weakly-typed C function pointer, which is kind of annoying in cases like this one.

The EventHandler is of type DerivedEventArgs but nevertheless points to a pfnBaseEventHandler function even though the function ran through the constructor.

That means, that std::tr1::function itself "supports" contravariance, but I can't find a way of simply getting the function pointer out of the std::tr1::funcion object if I don't know its type at compile time which is required for a template argument.

I would appreciate in cases like this that they added a simple get() method like they did for RAII pointer types.

Since I'm quite new to programming, I would like to know if there is a way to solve this problem, preferrably at compile-time via templates (which I think would be the only way).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

居里长安 2024-09-16 01:50:37

刚刚找到了问题的解决方案。看来我刚刚错过了在不同地点的演员阵容:

template<class TEventArgs>
class EventHandler : public std::function<void (void*, TEventArgs&)>
{
public:
    typedef void Signature(void*, TEventArgs&);
    typedef void (*HandlerPtr)(void*, TEventArgs&);

    // ...

    template<class TContravariantEventArgs>
    EventHandler(const EventHandler<TContravariantEventArgs>& rhs)
        : std::function<Signature>(reinterpret_cast<HandlerPtr>(*rhs.target<EventHandler<TContravariantEventArgs>::HandlerPtr>())) 
    {
        static_assert(std::is_base_of<TContravariantEventArgs, TEventArgs>::value,
            "The eventHandler instance to copy does not suffice the rules of contravariance.");
    }

    // ...
}

这就是它应该如何工作的方式。尽管如此,还是感谢您向我顺利介绍了这个非常棒的社区!

Just found a solution for the problem. It seems that I just missed a cast at a different location:

template<class TEventArgs>
class EventHandler : public std::function<void (void*, TEventArgs&)>
{
public:
    typedef void Signature(void*, TEventArgs&);
    typedef void (*HandlerPtr)(void*, TEventArgs&);

    // ...

    template<class TContravariantEventArgs>
    EventHandler(const EventHandler<TContravariantEventArgs>& rhs)
        : std::function<Signature>(reinterpret_cast<HandlerPtr>(*rhs.target<EventHandler<TContravariantEventArgs>::HandlerPtr>())) 
    {
        static_assert(std::is_base_of<TContravariantEventArgs, TEventArgs>::value,
            "The eventHandler instance to copy does not suffice the rules of contravariance.");
    }

    // ...
}

This works how it is supposed to work. Thank you nonetheless for giving me a smooth introduction into this really awesome community!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文