C застрял в бесконечном цикле присвоения равного оператора

#c

#c

Вопрос:

Я оказался в своего рода адском кошмаре, когда я пытаюсь использовать оператор equals для повторного присвоения объекта, который ссылается на себя через другой внутренний объект.

Целью этого проекта было

  1. Создайте объект с именем Foo
  2. Создайте внутренний объект с именем FooEventHandler , который содержит ссылку на Foo
  3. Передайте FooEventHandler в an EventEmitter , чтобы он мог вызывать Foo функции для событий

Я предоставил наименьший объем кода, чтобы проиллюстрировать цель и проблему одновременно. На сегодняшний день у меня не было проблем с моим Event модулем, включая мою парадигму, заключающуюся в том, что extended EventHandler s ссылаются на свои родительские объекты (в данном случае Foo ) и отправляются в EventEmitter , чтобы он мог вызывать любую Foo функцию, вроде как реализацию лямбда-функции.

Однако примерно через год использования этого дизайна я столкнулся с серьезным препятствием, когда мне нужно было сделать что-то вроде foo1 = foo2 (= operator) или Foo foo1 = foo2 (copy constructor). Я столкнулся с проблемой, когда ссылки не могут быть назначены ( FooEventHandler ссылка на Foo ). Итак, я пытаюсь исправить это, написав ctor и = operator ручного копирования, и теперь я застрял в бесконечном цикле для = оператора.

Пока я копаюсь в этом, я даже не знаю, чего я хочу достичь, не говоря уже о том, как это исправить. Одним из назначений = оператора было бы, когда я хочу обновить Foo объект, просто заменив его новым Foo объектом, например foo1 = foo2 . Но я кручу свои колесики, пытаясь выяснить, что я хочу сделать с Foo s EventHandler . foo1 s EventHandler все равно должен ссылаться на себя, так что, возможно, в = операторе я не переназначаю 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 amp;foo;
                FooEventHandler(Foo amp;foo)
                    :EventHandler(),
                     foo(foo)
                {
                    printf("FooEventHandler CONSTRUCTORn");   
                }
                FooEventHandler(const FooEventHandler amp;event_handler)
                    :EventHandler(),
                     foo(event_handler.foo)
                {
                    printf("FooEventHandler COPYn");   
                }
                FooEventHandler operator=(const FooEventHandleramp; 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 amp;foo)
            :event_handler(foo.event_handler)
        {
            printf("Foo COPYn"); 
        }
        Foo operator=(const Fooamp; 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;
}
  

Комментарии:

1. Выполните продуманный цикл с помощью отладчика, и вы обычно поймете, почему он зацикливается.

2. Foo operator=(const Fooamp; foo) обещает вернуть Foo , но не возвращает. Это вызовет проблемы. То же самое с FooEventHandler operator=(const FooEventHandleramp; event_handler)

3. У вас много ошибок и предупреждений , о которых вам следует позаботиться.

4. Примечание: ссылочные элементы обычно доставляют больше проблем, чем они того стоят. Вы не можете повторно назначить ссылку, поэтому, что бы вы ни делали, оператор присваивания всегда будет странным. В этом случае у вас есть только обработчик событий, и нет смысла пытаться указать его на другой, Foo поэтому вы могли бы создать оператор присваивания для FooEventHandler , который абсолютно ничего не делает, и удалить оператор присваивания (и конструктор копирования) из Foo , но это, вероятно, не делает того, что вам нужно, для неупорядоченной версии.

5. Удобное чтение: правило трех / пяти / нуля . Поскольку проблема решена FooEventHandler , ей нужно обработать три или пять, и как только это произойдет, Foo все будет в порядке и можно будет соблюдать правило нуля. Компилятор выполнит свою работу и создаст правильную специальную функцию-член, которая вызывает FooEventHandler пользовательскую функцию.

Ответ №1:

Вот бесконечный цикл. Эти функции вызывают друг друга.

 Foo(const Foo amp;foo)        :event_handler(foo.event_handler)
    {
        printf("Foo COPYn"); 
    }

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

Я думаю, что FooEventHandler не должен ссылаться на Foo из аспекта абстракции. Вам следует изменить свое решение.

Ответ №2:

Ответ X-Y: Устраните всю проблему, уничтожив источник проблемы: отдельный класс обработчика событий.

Приходит boost::function , чтобы предоставить универсальный функциональный интерфейс для обратного вызова события (почти) непосредственно Foo и boost::bind абстрагироваться от Foo -необходимости.

Обратите внимание, что здесь используется Boost, и установка Boost может быть затруднительной. Если в менеджере пакетов вашей операционной системы или среды разработки он готов к работе, вы также можете его использовать. Если нет, утешьтесь тем, что function и bind являются библиотеками только для заголовков, и их гораздо проще заставить работать, чем библиотеки, требующие компиляции.

 #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(Tamp;)> handler;
    std::vector<handler> handlers;
    void AddHandler(handler handle)
    {
        handlers.push_back(handle);
    }
    void EmitEvent(Tamp; 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 amp;)
    {
        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> amp; emitter,
        std::string /*name*/)
    {
        // Bind this and the handler function together
        emitter.AddHandler(boost::bind(amp;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 amp;)
    {
        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(amp;Foo::HandleEvent2, amp;foo, _1));
    std::string event;
    test.EmitEvent(event);
}
  

Неудачные попытки:

Foo содержит, EventHandler которому необходимо знать владельца Foo , чтобы вызвать его. Но что, если Foo ЭТО EventHandler ? 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 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(amp;foo1);
    test.EmitEvent();
}

  

Вся проблема просто исчезает и оставляет вам гораздо более простой код. Сохранил это, потому что это действительно просто.

Но это не удовлетворит потребности пользователя. Я написал это перед тем, как перечитать вопрос и требование C 98. Он использует std::function обратный вызов события for и лямбда-выражения для замены 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(Tamp;)>; // simplify naming
    std::vector<handler> handlers;
    void AddHandler(handler handle)
    {
        handlers.push_back(handle);
    }
    void EmitEvent(Tamp; evt)
    {
        // range-based for loop. Can't go out of bounds
        for (const auto amp; handle : handlers)
        {
            handle(evt); // call the function
        }
    }
};

class Foo
{
private:
    // we can hide the event handlers away from prying eyes.
    void HandleEvent(std::string amp;)
    {
        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> amp; emitter,
        std::string /*name*/)
    {
        // install lambda expression as handler function
        // lambda captures this so it knows which Foo to call
        emitter.AddHandler([this](std::stringamp; 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 amp;)
    {
        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([amp;foo](std::stringamp; evt)
                {
                    foo.HandleEvent2(evt); // wrap public handler function
                    return;
                });

    std::string event;
    test.EmitEvent(event);
}
  

Не все так просто, но обработка нескольких типов событий обречена быть более сложной. Сохранено, потому что запрашивающий не единственный человек там.

Комментарии:

1. К сожалению, мои обработчики событий являются шаблонами, т.Е. EventHandler<T> . Кто-то вроде Foo захочет обработать более одного события. Это сработало бы, если бы каждая Event функция была другого типа, тогда могло бы быть много HandleEvent функций с разными сигнатурами, и я Emtiter мог бы определить, какую из них использовать. Но как только есть два Event s, которые, например, имеют тип std::string и Foo заботятся об обоих, я бы отключился. Я согласен, что редизайн необходим.

2. @ony_pox232 У меня нет boost, доступного для тестирования boost-версии этого. Тем не менее, если вы застряли на c 98, у вас, вероятно, тоже не будет Boost.

3. @пользователь4581301 wandbox.org это онлайновый компилятор, в который встроен boost

4. Вероятно, есть элегантный способ сделать это, о котором я не думаю, но на данный момент я бы вернулся к C. Функция события получает дополнительный void* параметр и устанавливает static функции-члены. static Функция-член приводит void* к правильному типу, а затем использует его для вызова правильной функции.

5. @JerryJeremiah честно говоря, я всегда с опаской говорю людям использовать Boost, если они им еще не пользуются. Это большой молоток, и для начала работы требуется немного поломать голову.