繁体   English   中英

C++ 陷入相等运算符分配的无限循环

[英]C++ stuck in infinite loop of equal operator assignment

我发现自己陷入了一种地狱般的噩梦,我试图使用等号运算符重新分配一个 object,它通过另一个内部 object 引用自己。

这个设计的目标是

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

我提供了最少量的代码来同时说明目标和问题。 迄今为止,我的Event模块没有任何问题,包括我的扩展EventHandler引用其父对象(在本例中为Foo )并将其发送到EventEmitter以便它可以调用任何Foo function 的范例,有点像lambda function 的实现。

然而,在使用这种设计大约一年后,当我需要执行诸如foo1 = foo2 (= operator) 或Foo foo1 = foo2 (copy constructor) 之类的事情时,我遇到了一个主要障碍。 我遇到了无法分配引用的问题( FooEventHandlerFoo的引用)。 所以我试图通过编写手动复制ctor和=运算符来解决这个问题,现在我陷入了=运算符的无限循环。

当我挖掘这个时,我什至不知道我想要完成什么,更不用说如何解决它了。 =运算符的一个目的是当我想通过简单地用新的Foo Foo时,例如foo1 = foo2 但是,我正在转动我的轮子,试图弄清楚我想用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 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 回答:通过杀死问题的根源来解决整个问题 go:单独的事件处理程序 class。

进来boost::function提供一个通用的 function 接口,用于事件回调(几乎)直接到Fooboost::bind以抽象出Foo -ness。

请注意,这使用了 Boost,而 Boost 安装起来很麻烦。 如果您的操作系统或开发环境的 package 管理器已准备好 go,不妨使用它。 如果没有,请放心使用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 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);
}

失败的尝试:

Foo包含一个EventHandler ,它需要知道拥有的Foo才能调用它。 但是如果FooEventHandler呢? Foo实现EventHandler消除了问题的循环性质,因为没有人像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 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();
}

整个问题就消失了,给你留下了更简单的代码。 保留这个,因为它真的很简单。

但它不会满足提问者的需求。 我在重新阅读问题和 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 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);
}

不是那么简单,但处理多种类型的事件注定会更加复杂。 保留,因为提问者不是唯一的人。

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

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");   
            }

我认为 FooEventHandler 不应该从抽象方面引用 Foo 。 你应该改变你的设计。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM