可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have one base class which holds a map
for function pointers like this
typedef void (BaseClass::*event_t)();
class BaseClass {
protected:
std::map<std::string, event_t> events;
public:
// Example event
void onFoo() {
// can be added easily to the map
}
};
Handling this works prefect, but now i want to make BaseClass
an abstract base class to derive from like this:
class SpecificClass : public BaseClass {
public:
void onBar() {
// this is gonna be difficult!
}
};
Although i can access the map from SpecificClass
i am not able to add onBar
because the event_t
type is only defined for the BaseClass
! Is there any possibility (maybe with templates?) which does not lead to define the event_t
for each class i will use...
(It is not neccessary to use templates! Any good/suitable approach would be nice.)
More background information:
This whole thing is for a text based RPG. My base class could be called Location
and the specifc one any location e.g. CivicCenter
. Each Location
object subscribes to my EventSystem
which notifies all neccessary objects when i fire an event. Therefore i want to store in a map some pointers to private functions holding the actions with their "name" like onSetOnFire
(xD) as the key.
回答1:
This can't be done with your current map
as it stands. Think about what would happen if you could put a child method into the map. Then you could pull a pointer-to-child-member (masquerading as base) out of the map, call it on a base class instance pointer, and then how would it call a derived class on a base class instance which obviously couldn't work.
Would a polymorphic approach work?
回答2:
Yes; stop using member pointers.
The more correct way of doing what you want is to have an event type and an object pointer. So an event fires on a specific object. The event type would be a non-member function (or a static member). It would be passed the object pointer. And it would call some actual member function of that object.
Nowadays, the event type could be a std/boost::function
. However, since the function parameters have to stay the same type for all events, this doesn't really fix your problem. You can't call SpecificClass::onBar
from a BaseClass
pointer unless you do a cast to a SpecificClass
. And the event calling function would not know to do this. So you still can't put SpecificClass::onBar
in the std/boost::function
object; you still need some standalone function to do the cast for you.
This all just seems to be a terrible use of polymorphism. Why does SpecificClass
need to derive from BaseClass
at all? Can't they just be two unrelated classes?
回答3:
After some thought and a redesign i was able to achieve what i wanted. Although i am stubborn and still using inheritance i have reimplemented the map. This is how it works now:
class Location {
// ...
protected:
std::map<std::string, std::function<void(void)>> m_mEvents;
};
And now i can handle it like this:
class CivicCenter : public Location {
public:
CivicCenter() {
// this is done by a macro which lookes better than this
this->m_mEvents["onTriggerSomething"] =
std::bind(&CivicCenter::onTriggerSomething, this);
}
void onTriggerSomething() {
// ...
}
// ...
};
With easy use of std::bind
i am able to implement generic function pointers. When using parameters like in std::function<void(int, int)>
remeber to use either boost's _1
and _2
or lambda expressions like me:
std::function<void(int,int)> f = [=](int a, int b) {
this->anotherFunctionWithParams(a, b);
};
But this is just pointed out due to completeness of my solution.
回答4:
You have to use static_cast
:
event_t evt = static_cast<event_t>(&SpecificClass::onBar);
This is because it is slightly dangerous to cast to event_t
, you could accidently apply it to a BaseClass
instance.
How it works (for the skeptical):
class BaseClass {
public:
typedef void (BaseClass::*callback_t)(); // callback method
void doSomething(callback_t callback) {
// some code
this->*callback();
// more code
}
void baseCallback(); // an example callback
};
class DerivedClass : public BaseClass {
public:
void derivedCallback();
void doWhatever() {
// some code
doSomething(&BaseClass::baseCallback);
// more code
doSomething(static_cast<callback_t>(&DerivedClass::derivedCallback));
// et cetera
};
Here is what you should avoid, and why this is potentially dangerous:
void badCodeThatYouShouldNeverWrite()
{
BaseClass x;
// DO NOT DO THIS IT IS BAD
x.doSomething(static_cast<callback_t>(&DerivedClass::derivedCallback));
}
The requirement for a static_cast
makes it so you can't "accidentally" pass DerivedClass
method pointers in. And if you think this is dangerous, just remember that it's a pointer, and pointers are always dangerous. Of course, there are ways you can do this that involve creating helper classes, but that requires a lot of extra code (possibly making a class for every function you want to pass as a callback). Or you could use closures in C++11, or something from Boost, but I realize that a lot of us do not have that option.