不同模板类型的多重继承

发布于 2024-08-16 07:58:37 字数 1403 浏览 1 评论 0原文

我正在 C++ 中进行事件处理并处理事件通知,我有一个 EventGenerator 类,任何生成事件的类都可以从中继承。 EventGenerator 有一个其他类可以用来添加回调的方法,以及一个在事件发生后调用回调的方法。

为了处理不同类型事件的通知,我在模板类型 T 上对 EventGenerator 进行了参数化,然后通知程序类可以继承EventGenerator 在不同类型上多次参数化。

为了完整起见,以下是 EventGenerator 的代码

#ifndef _EventGenerator
#define _EventGenerator

#include <list>

#include "EventListener.h"

template <class Event>
class EventGenerator {
private: 
    std::list<EventListener<Event>*> listeners;
protected:
    EventGenerator() {}

    void changeEvent(Event event) {
        std::list<EventListener<Event>*>::const_iterator it = listeners->begin();
        for (; it != listeners->end(); it++) {
            (*it)->changeEvent(event);
        }
    }

public:
    void addListener(EventListener<Event>* listener) {
            listeners->push_back(listener);
        }
};

#endif



这是 EventListener 的代码,任何想要添加回调的类都继承自 -

#ifndef _EventListener
#define _EventListener

template <class Event>
class EventListener {
private:
    EventListener(const EventListener<Event>& event);
protected:
    EventListener() {}
public:
    virtual void changeEvent(Event event) = 0;
};

#endif



我感觉这不是一个很好的设计,并且想知道是否有更好的设计来解决这样的问题。

编辑:令人烦恼的是我正在使用多重继承。我经常被警告不要使用它,所以我想我想知道这样的设计是否会导致将来发生不好的事情

谢谢

I'm working on event handling in C++ and to handle notification of events, I have a class EventGenerator which any class generating events can inherit from. EventGenerator has a method which other classes can use to add in callbacks and a method to call the callbacks once an event happens

To handle notification of different types of events, I've parametrized EventGenerator on template type T and the notifier class can then inherit from EventGenerator multiple times parametrized on different types.

For the sake of completeness, here's the code for EventGenerator

#ifndef _EventGenerator
#define _EventGenerator

#include <list>

#include "EventListener.h"

template <class Event>
class EventGenerator {
private: 
    std::list<EventListener<Event>*> listeners;
protected:
    EventGenerator() {}

    void changeEvent(Event event) {
        std::list<EventListener<Event>*>::const_iterator it = listeners->begin();
        for (; it != listeners->end(); it++) {
            (*it)->changeEvent(event);
        }
    }

public:
    void addListener(EventListener<Event>* listener) {
            listeners->push_back(listener);
        }
};

#endif

and here's the code for EventListener which any class which wants to add callbacks inherits from -

#ifndef _EventListener
#define _EventListener

template <class Event>
class EventListener {
private:
    EventListener(const EventListener<Event>& event);
protected:
    EventListener() {}
public:
    virtual void changeEvent(Event event) = 0;
};

#endif

I've a feeling this is not a very good design and was wondering if there was a better design out there for such a problem.

Edit: What bothers is the fact that I'm using multiple inheritance. I've been frequently warned against using it so I guess I wanted opinions on whether such a design could lead to bad things happening in the future

Thanks

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

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

发布评论

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

评论(4

木槿暧夏七纪年 2024-08-23 07:58:38

谨防钻石继承体系。另请注意,重载虚拟函数是一件坏事。所以如果你有这样的事情:

class Handler : public EventHandler<int>, public EventHandler<string> { ... };

哪个changeEvent()函数将被调用?别指望它!

如果您小心的话,上面的代码应该没问题,但如果您想完全避免继承,那么我建议使用与某些唯一标识符关联的函数引用。举个例子:

class Listener { public: virtual ~Listener ( ) { } };
template<typename Event> class Distributor : public Listener {
    public:
    void addListener (shared_ptr<Listener>, function<void (Event)>);
    void listen (Event e) { 
        for_each(_listeners.begin(), _listeners.end(), 
            bind(&ListenNode::listen, _1, e));
    }
    private:
    struct ListenNode { 
        weak_ptr<Listener> listener;
        function<void (Event)> callback;
        void listen (Event e) {
            shared_ptr<Listener> l = listener.lock();
            if(l) callback(e);
        }
    };
    list<ListenNode> _listeners;
};

通过这种设置,所有侦听器实际上都派生自一个基类。监听器可以注册多个回调,并且分发器可以链接。当然,您不必使用shared_ptr,但我喜欢它们,因为它们省去了取消注册侦听器的麻烦。您可以以任何您喜欢的方式注册回调,将它们与字符串、整数或其他任何内容相关联。

我省略了很多细节,事件分发是一件复杂的事情。我认为 Andrei Alexandrescu 写了一篇关于该主题的详细文章,请查阅。

Beware of diamond inheritance heirarchies. Also note that overloading virtual functions is a bad thing. So if you have something like this:

class Handler : public EventHandler<int>, public EventHandler<string> { ... };

Which changeEvent() function will be called? Don't count on it!

If you are careful the above code should be fine, but if you want to avoid inheritance altogether then I suggest using function references associated with some unique identifier. As an example:

class Listener { public: virtual ~Listener ( ) { } };
template<typename Event> class Distributor : public Listener {
    public:
    void addListener (shared_ptr<Listener>, function<void (Event)>);
    void listen (Event e) { 
        for_each(_listeners.begin(), _listeners.end(), 
            bind(&ListenNode::listen, _1, e));
    }
    private:
    struct ListenNode { 
        weak_ptr<Listener> listener;
        function<void (Event)> callback;
        void listen (Event e) {
            shared_ptr<Listener> l = listener.lock();
            if(l) callback(e);
        }
    };
    list<ListenNode> _listeners;
};

With this setup, all listeners derive from one base class virtually. Listeners can have multiple callbacks registered, and Distributors can be chained. Of course you don't have to use shared_ptr's but I like them because they save from the hassle of unregistering listeners. You can register the callbacks any way you like, associating them with a string, integer or whatever.

I have omitted a lot of detail, event distribution is a complicated business. I think Andrei Alexandrescu wrote a detailed article on the topic, look it up.

等待圉鍢 2024-08-23 07:58:38

正如其他人所说,您可能会遇到钻石继承问题。此外,从事件处理程序基继承可能违反单一职责原则。我要做的是在需要了解某些事件的类中使用嵌套类:

class CNeedsToHandleEvent {
//...
private:

  void OnChange (Event event) {
    //Do processing of the event
  }

  class ChangeEventHandler : public EventListener {
    virtual void changeEvent(Event event) {
      CNeedsToHandleEvent* parent = OUTERCLASS(CNeedsToHandleEvent, m_ChangeEventHandler);
      parent->OnChange(event);
    }
  } m_ChangeEventHandler;

  friend class ChangeEventHandler;
};

这是 OUTERCLASS 宏。有些人可能认为它的使用有争议,也许它可能存在可移植性问题,但它非常方便:

// Get a pointer to outer class of a nested class
#ifndef OUTERCLASS
#define OUTERCLASS(className, memberName) \
    reinterpret_cast<className*>(reinterpret_cast<unsigned char*>(this) - offsetof(className, memberName))
#endif

您始终可以使用指向父类的指针实例化嵌套类,以便它可以调用真正的处理程序而不是依赖宏。

As others have said you might run into diamond inheritance issues. Also, inheriting from an event handler base can violate Single Responsibility Principle. What I would do is use a nested class inside a class that needs to know about some event:

class CNeedsToHandleEvent {
//...
private:

  void OnChange (Event event) {
    //Do processing of the event
  }

  class ChangeEventHandler : public EventListener {
    virtual void changeEvent(Event event) {
      CNeedsToHandleEvent* parent = OUTERCLASS(CNeedsToHandleEvent, m_ChangeEventHandler);
      parent->OnChange(event);
    }
  } m_ChangeEventHandler;

  friend class ChangeEventHandler;
};

And here is the OUTERCLASS macro. Some might consider its use controversional and perhaps it may have portability issues, but it's pretty convinient:

// Get a pointer to outer class of a nested class
#ifndef OUTERCLASS
#define OUTERCLASS(className, memberName) \
    reinterpret_cast<className*>(reinterpret_cast<unsigned char*>(this) - offsetof(className, memberName))
#endif

You can always instantiate the nested class with a pointer to the parent class so that it can call the real handlers instead of relying on the macro.

君勿笑 2024-08-23 07:58:38

这是糟糕的设计,因为在您的情况下,当您分派事件时,事情可能无法正常工作......除非您有一些聪明的元编程方法来迭代您派生的所有类型。

但总的来说,我不认为多重继承或此类继承有什么问题。

It's bad design because in your case things probably aren't going to work when you dispatch events... Unless you have some clever meta-programming way to iterate over all the types you're derived from.

In general though, I don't think there's anything wrong with multiple inheritance in general, or of this sort.

淤浪 2024-08-23 07:58:38

当您创建从使用事件的多个类派生的类时,如果这些父类使用相同的事件,则可能会遇到问题。

值得注意的是,您将需要使用以下语法来定义抽象回调函数:

template<class T>
class Base
{
    virtual void listener() = 0;
}

class Derived
    : public class Base<int>
    , public class Base<float>
{
    void Base<int>::listener(){...}
    void Base<float>::listener(){...}
}

如果您需要从 Derived 调用其中任何一个,您可能需要使用类似的语法(虽然我真的不知道),但是如果您从 Base 或 Base 引用调用它,那么一切都会很好地工作。作为额外的好处,除非实际定义了侦听器函数,否则您不会编译。

或多或少,多重继承意味着您需要更多关注,但这是该语言的一个有价值的特性。我认为您可能还可以采取更多模板恶作剧来防止钻石继承,但代价是优雅。

You might run into problems as you make classes that are derived on multiple classes that use events, should those parent classes use the same events.

Of other note, you will need to use the following syntax to define your abstract callback function:

template<class T>
class Base
{
    virtual void listener() = 0;
}

class Derived
    : public class Base<int>
    , public class Base<float>
{
    void Base<int>::listener(){...}
    void Base<float>::listener(){...}
}

If you then need to call either of those from Derived, you probably would need to use similar syntax (Although I honestly don't know), but if you call it from a Base or Base reference, it'll all work nicely. As an added bonus, you won't compile unless the listener functions are actually defined.

More or less, multiple inheritance means you need to pay more attention, but it's a valuable feature of the language. I think there might also be more template shenanigans you can do to prevent diamond inheritance, at the cost of elegance.

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