#c
#c
Вопрос:
Я оказался в своего рода адском кошмаре, когда я пытаюсь использовать оператор equals для повторного присвоения объекта, который ссылается на себя через другой внутренний объект.
Целью этого проекта было
- Создайте объект с именем
Foo
- Создайте внутренний объект с именем
FooEventHandler
, который содержит ссылку наFoo
- Передайте
FooEventHandler
в anEventEmitter
, чтобы он мог вызывать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, если они им еще не пользуются. Это большой молоток, и для начала работы требуется немного поломать голову.