C++陷入了相等运算符赋值的无限循环



我发现自己陷入了一场地狱般的噩梦,我试图使用equals运算符来重新分配一个通过另一个内部对象引用自己的对象。

这个设计的目标是

  1. 创建一个名为Foo的对象
  2. 创建一个名为FooEventHandler的内部对象,该对象包含对Foo的引用
  3. FooEventHandler传递给EventEmitter,以便它可以在事件上调用Foo函数

我提供了最少量的代码来同时说明目标和问题。到目前为止,我的Event模块还没有遇到任何问题,包括让扩展的EventHandler引用它们的父对象(在本例中为Foo(并被发送到EventEmitter,这样它就可以调用任何Foo函数,有点像lambda函数的实现。

然而,在使用这个设计大约一年后,当我需要做foo1 = foo2(=操作符(或Foo foo1 = foo2(复制构造函数(之类的事情时,我遇到了一个主要的障碍。我遇到了引用不可分配的问题(FooEventHandlerFoo的引用(。所以我试图通过编写手动复制ctor和=运算符来解决这个问题,现在我陷入了=运算符的无限循环中。

当我深入研究这个问题时,我甚至不知道我想完成什么,更不用说如何修复它了。=运算符的一个目的是当我想通过用新的Foo对象(例如foo1 = foo2(替换Foo对象来更新它时。但是,我正在转动我的轮子,试图弄清楚我想用FooEventHandler做什么。foo1EventHandler应该仍然引用它自己,所以也许在=运算符中,我不会重新分配EventHandler。。但是,也许我这样做是因为foo1应该是=foo2,其EventHandler引用foo2!。。或者可能不会。。或者可能是的??!

我希望有人能看看这个问题,给我一些明确的我应该做什么

注意:我在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 CONSTRUCTORn");   
}
FooEventHandler(const FooEventHandler &event_handler)
:EventHandler(),
foo(event_handler.foo)
{
printf("FooEventHandler COPYn");   
}
FooEventHandler operator=(const FooEventHandler& event_handler) {
printf("FooEventHandler =n");   
this->foo = event_handler.foo;
}
~FooEventHandler(){
printf("FooEventHandler DESTRUCTORn");   
}
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 CONSTRUCTORn");   
}
Foo(const Foo &foo)
:event_handler(foo.event_handler)
{
printf("Foo COPYn"); 
}
Foo operator=(const Foo& foo)
{
printf("Foo =n");    
this->event_handler = foo.event_handler;
}
~Foo(){
printf("Foo DESTRUCTORn");   
}
void HandleSomeEvent(/*some event*/){
printf("Look at me handling an event");
}
};
int main()
{
printf("Foo1 createn");
Foo foo1("a");
printf("Foo2 equaln");
Foo foo2("b");
// start infinite loop of ='s
foo2 = foo1;
}

这里是无限循环。这些函数相互调用。

Foo(const Foo &foo)        :event_handler(foo.event_handler)
{
printf("Foo COPYn"); 
}
FooEventHandler(const FooEventHandler &event_handler)
:EventHandler(),
foo(event_handler.foo)
{
printf("FooEventHandler COPYn");   
}

我认为FooEventHandler不应该从抽象的角度引用Foo。你应该改变主意。

X-Y答案:通过杀死问题的来源:单独的事件处理程序类,使整个问题消失。

boost::function(几乎(直接为Fooboost::bind提供了一个用于事件回调的通用函数接口,以抽象掉Foo

请注意,这使用Boost,安装Boost可能会很麻烦。如果您的操作系统或开发环境的软件包管理器已经准备好使用它,那么不妨使用它。如果没有,请放心functionbind是仅头部的库,并且比需要编译的库更容易工作。

#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 eventn");
}
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 CONSTRUCTORn");
}
~Foo()
{
printf("Foo DESTRUCTORn");
}
// 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 eventnOK. It's the same event, but it proves the point.n");
}
};
int main()
{
printf("Foo1 createn");
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);
}

失败的尝试:

Foo包含一个EventHandler,它需要知道拥有的Foo才能调用它。但是如果Foo就是EventHandler呢?实现EventHandlerFoo消除了问题的循环性质,因为没有人像Foo那样了解Foo

#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 eventn");
}
Foo(std::string /*name*/)
{
printf("Foo CONSTRUCTORn");
}
~Foo(){
printf("Foo DESTRUCTORn");
}
};
int main()
{
printf("Foo1 createn");
Foo foo1("a");
// start infinite loop of ='s
EventEmitter test;
test.AddHandler(&foo1);
test.EmitEvent();
}

整个问题消失了,留给您的代码要简单得多。保持这个,因为它真的很简单。

但它不能满足阿斯克的需求。我在重读这个问题和C++98要求之前写了这篇文章。它使用std::function进行事件回调,并使用Lambda表达式替换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 eventn");
}
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 CONSTRUCTORn");
}
~Foo()
{
printf("Foo DESTRUCTORn");
}
// 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 eventnOK. It's the same event, but it proves the point.n");
}
};
int main()
{
printf("Foo1 createn");
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);
}

虽然没有那么简单,但处理多种类型的事件注定会更加复杂。因为阿斯克并不是唯一一个人。

最新更新