I've found myself in a sort of hellish nightmare where I am trying to use the equals operator to re-assign an object that references itself through another internal object.
The goal of this design was to
Foo
FooEventHandler
that contains a reference to Foo
FooEventHandler
around to an EventEmitter
so that it can call Foo
functions on eventsI've provided the tiniest amount of code to illustrate the goal and the problem at the same time. I've had no issues with my Event
module to date, including my paradigm of having extended EventHandler
s reference their parent objects (in this case Foo
) and be sent to an EventEmitter
so that it can call any Foo
function, sort of like an implementation of a lambda function.
However, after about a year of using this design, I hit a major road block when I needed to do something like foo1 = foo2
(= operator) or Foo foo1 = foo2
(copy constructor). I ran into the issue with references not being assignable ( FooEventHandler
's reference to Foo
). So I am trying to fix that by writing manual copy ctor and =
operator, and now I am stuck in an infinite loop for the =
operator.
As I dig through this, I don't even know what I want to accomplish let alone how to fix it. One purpose of the =
operator would be when I want to update a Foo
object by simply replacing it with a new Foo
object, eg foo1 = foo2
. But, I am spinning my wheels trying to figure out what I want to do with Foo
's EventHandler
. foo1
's EventHandler
should still reference itself, so maybe in the =
operator I don't re-assign the EventHandler
.. but, maybe I do because foo1
should be =
to foo2
whose EventHandler
references foo2
... or maybe not.? or maybe yes??!
I am hoping someone can look at this problem and give me some clarity on what I should do.
Notes: I am in c++ 98
#include <iostream>
#include <string>
#include <vector>
// EventHandler and EventEmitter are just included to display my intent
class EventHandler {
public:
virtual ~EventHandler(){}
virtual void HandleEvent(/*some event*/) = 0;
};
class EventEmitter {
public:
std::vector<EventHandler*> handlers;
void AddHandler(EventHandler *handler){
this->handlers.push_back(handler);
}
void EmitEvent(/*some event*/){
for(size_t i = 0; i < this->handlers.size(); i++){
this->handlers.at(i)->HandleEvent(/*some event*/);
}
}
};
// The problem arises in Foo/FooEventHandler with circular references
class Foo {
public:
// This object is designed to carry Foo to the EventEmitter
class FooEventHandler : public EventHandler {
public:
Foo &foo;
FooEventHandler(Foo &foo)
:EventHandler(),
foo(foo)
{
printf("FooEventHandler CONSTRUCTOR\n");
}
FooEventHandler(const FooEventHandler &event_handler)
:EventHandler(),
foo(event_handler.foo)
{
printf("FooEventHandler COPY\n");
}
FooEventHandler operator=(const FooEventHandler& event_handler) {
printf("FooEventHandler =\n");
this->foo = event_handler.foo;
}
~FooEventHandler(){
printf("FooEventHandler DESTRUCTOR\n");
}
void HandleEvent(/*some event*/){
this->foo.HandleSomeEvent();
}
};
// Foo is just some generic object with a custom handler to ref itself
FooEventHandler event_handler;
Foo(std::string name)
:event_handler(*this)
{
printf("Foo CONSTRUCTOR\n");
}
Foo(const Foo &foo)
:event_handler(foo.event_handler)
{
printf("Foo COPY\n");
}
Foo operator=(const Foo& foo)
{
printf("Foo =\n");
this->event_handler = foo.event_handler;
}
~Foo(){
printf("Foo DESTRUCTOR\n");
}
void HandleSomeEvent(/*some event*/){
printf("Look at me handling an event");
}
};
int main()
{
printf("Foo1 create\n");
Foo foo1("a");
printf("Foo2 equal\n");
Foo foo2("b");
// start infinite loop of ='s
foo2 = foo1;
}
XY answer : Make the whole problem go away by killing the source of the problem: the separate event handler class.
In comes boost::function
to supply a generic function interface for event callback (almost) directly to Foo
and boost::bind
to abstract away the Foo
-ness.
Note that this uses Boost, and Boost can be a pain to install. If your OS or Development environment's package manager has it ready to go, might as well use it. If not, take comfort in function
and bind
being header-only libraries and much easier to get working than the libraries that need compiling.
#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
#include <boost/function.hpp>
#include <boost/bind.hpp>
template<class T> // now a template to allow for different types of events
class EventEmitter
{
public:
typedef boost::function<void(T&)> handler;
std::vector<handler> handlers;
void AddHandler(handler handle)
{
handlers.push_back(handle);
}
void EmitEvent(T& evt)
{
// No range-for. Oh well. Still no need for at. The loop bounds make
// overrun impossible
for (size_t i = 0; i < handlers.size(); i++)
{
handlers[i](evt); // call the function
}
}
};
class Foo
{
private:
// we can hide the event handlers away from prying eyes.
void HandleEvent(std::string &)
{
printf("Look at me handling a string event\n");
}
public:
// Foo might as well register itself on construction, so passing in the emitter
Foo(EventEmitter<std::string> & emitter,
std::string /*name*/)
{
// Bind this and the handler function together
emitter.AddHandler(boost::bind(&Foo::HandleEvent, this, _1));
printf("Foo CONSTRUCTOR\n");
}
~Foo()
{
printf("Foo DESTRUCTOR\n");
}
// but if we want to install event handlers later, here's a public function
void HandleEvent2(std::string &)
{
printf("Look at me handling a different string event\nOK. It's the same event, but it proves the point.\n");
}
};
int main()
{
printf("Foo1 create\n");
EventEmitter<std::string> test;
Foo foo(test, "a");
// same as above, but this time with foo in place of this
test.AddHandler(boost::bind(&Foo::HandleEvent2, &foo, _1));
std::string event;
test.EmitEvent(event);
}
Failed attempts:
Foo
contains an EventHandler
that needs to know the owning Foo
in order to call it. But what if Foo
IS the EventHandler
? Foo
implementing EventHandler
eliminates the circular nature of the problem because no one knows Foo
quite like Foo
does.
#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
// EventHandler and EventEmitter are just included to display my intent
class EventHandler {
public:
virtual ~EventHandler(){}
virtual void HandleEvent(/*some event*/) = 0;
};
class EventEmitter {
public:
std::vector<EventHandler*> handlers;
void AddHandler(EventHandler *handler){
this->handlers.push_back(handler);
}
void EmitEvent(/*some event*/){
for(size_t i = 0; i < this->handlers.size(); i++){
this->handlers.at(i)->HandleEvent(/*some event*/);
}
}
};
// Foo IS the FooEventHandler
class Foo: public EventHandler {
public:
void HandleEvent(/*some event*/){
// doesn't need to know Foo. It IS Foo
printf("Look at me handling an event\n");
}
Foo(std::string /*name*/)
{
printf("Foo CONSTRUCTOR\n");
}
~Foo(){
printf("Foo DESTRUCTOR\n");
}
};
int main()
{
printf("Foo1 create\n");
Foo foo1("a");
// start infinite loop of ='s
EventEmitter test;
test.AddHandler(&foo1);
test.EmitEvent();
}
The whole problem just goes away and leaves you with much simpler code. Kept this because it is really simple.
But it will not meet the Asker's needs. I wrote this before re-reading the question and the C++98 requirement. It uses std::function
for event callback and Lambda Expressions to replace boost::bind
.
#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
#include <functional>
template<class T> // now a template to allow for different types of events
class EventEmitter
{
public:
using handler = std::function<void(T&)>; // simplify naming
std::vector<handler> handlers;
void AddHandler(handler handle)
{
handlers.push_back(handle);
}
void EmitEvent(T& evt)
{
// range-based for loop. Can't go out of bounds
for (const auto & handle : handlers)
{
handle(evt); // call the function
}
}
};
class Foo
{
private:
// we can hide the event handlers away from prying eyes.
void HandleEvent(std::string &)
{
printf("Look at me handling a string event\n");
}
public:
// Foo might as well register itself on construction, so passing in the emitter
Foo(EventEmitter<std::string> & emitter,
std::string /*name*/)
{
// install lambda expression as handler function
// lambda captures this so it knows which Foo to call
emitter.AddHandler([this](std::string& evt)
{
HandleEvent(evt); //call wrapped function
return;
});
printf("Foo CONSTRUCTOR\n");
}
~Foo()
{
printf("Foo DESTRUCTOR\n");
}
// but if we want to install event handlers later, here's a public function
void HandleEvent2(std::string &)
{
printf("Look at me handling a different string event\nOK. It's the same event, but it proves the point.\n");
}
};
int main()
{
printf("Foo1 create\n");
EventEmitter<std::string> test;
Foo foo(test, "a");
// install outside of foo
// lambda captures foo by reference. We don't want to operator on a copy
test.AddHandler([&foo](std::string& evt)
{
foo.HandleEvent2(evt); // wrap public handler function
return;
});
std::string event;
test.EmitEvent(event);
}
Not quite as simple, but handling multiple types of events is doomed to be more complicated. Kept because the Asker's not the only person out there.
Here is the infinite loop. These functions call each other.
Foo(const Foo &foo) :event_handler(foo.event_handler)
{
printf("Foo COPY\n");
}
FooEventHandler(const FooEventHandler &event_handler)
:EventHandler(),
foo(event_handler.foo)
{
printf("FooEventHandler COPY\n");
}
I think FooEventHandler should not has a reference to Foo from aspect of abstraction. You should change your desing.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.