Dirk Groeneveld
Dirk Groeneveld

Reputation: 2627

Event handling: Sending events from base classes when the receiver expects derived classes

This code distills the problem to its essence:

Infrastructure classes:

struct EventReceiverBase {
    virtual ~EventReceiverBase() { }
};

template<typename T>
struct EventReceiver : public virtual EventReceiverBase {
    virtual void receiveEvent(T* pSender) = 0;
};

struct EventSender {
    EventReceiverBase* pReceiver;

    template<typename T>
    void sendEvent(T* pSender) {
        EventReceiver<T>* pCastedReceiver =
            dynamic_cast<EventReceiver<T>*>(pReceiver);
        // HERE IS THE PROBLEM
        // pCastedReceiver is null. T is BaseSender. The pointer
        // being casted is really of type EventReceiver<DerivedSender>*,
        // but it tries to cast to EventReceiver<BaseSender>*, and that
        // fails.
        pCastedReceiver->receiveEvent(pSender);
    }
};

User classes:

struct BaseSender : public virtual EventSender {
    void f() {
        sendEvent(this);
    }
};
struct DerivedSender : public BaseSender { };

struct MyClass : public virtual EventReceiver<DerivedSender> {
    void receiveEvent(DerivedSender* pSender) { }
};

int main() {
    MyClass my;
    DerivedSender derivedSender;
    derivedSender.pReceiver = &my;
    derivedSender.f();
}

Can I recast this problem (no pun intended) to avoid this issue? I want to keep the user classes as simple as possible, while exposing the event sending and receiving functionality as close to this way as possible.

For example, I can "fix" it by making MyClass derive from EventReceiver<BaseSender> as well, but I'd really like to avoid that, as it would mean extra work in every class that receives events.

Edit: Executable paste: http://liveworkspace.org/code/4bm6OU$13

Upvotes: 1

Views: 586

Answers (2)

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726599

The reason why you see a problem has to do with the placement of the f function in the BaseSender: in the call to sendEvent below, this represents a pointer to BaseSender. Essentially, the call below

void f() {
    sendEvent(this);
}

is a short way to write this:

void f() {
    sendEvent<BaseSender>(this);
}

Moving f to DerivedSender fixes this problem.

Another alternative is making BaseSender::f a template:

template <typename T>
void f() {
    sendEvent<T>((T*)this);
}

and calling it like this:

derivedSender.f<DerivedSender>();

This does not look particularly nice, but may be a work-around to code copy-pasting in situations when the real-world f is large.

Yet another solution is to make BaseSender a template, like this:

template<typename T>
struct BaseSender : public virtual EventSender {
    void f() {
        sendEvent((T*)this);
    }
};
struct DerivedSender : public BaseSender<DerivedSender> {
};

This also works (link to ideone).

Upvotes: 1

Mats Petersson
Mats Petersson

Reputation: 129374

This shouldn'e be a problem. pSender will point to the orignal object, unless you are doing something really weird.

However, your receiveEvent in MyClass should take a BaseSender * object, you'll then have to cast it to DerivedSender (or better yet, only use virtual methods defined in the base - this is how you SHOULD do things in general)

Upvotes: 0

Related Questions