简体   繁体   中英

C++ stuck in infinite loop of equal operator assignment

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

  1. Create an object called Foo
  2. Create an internal object called FooEventHandler that contains a reference to Foo
  3. Pass FooEventHandler around to an EventEmitter so that it can call Foo functions on events

I'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.

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