c++ - 多态性和函数绑定
问题描述
对于我正在编写的事件系统,我想将回调绑定到函数列表。
这是我想做的一个基本示例:
#include <iostream>
#include <functional>
#include <string>
class Base {
public:
virtual std::string getType() const = 0;
};
class Derived : public Base {
protected:
int some_data;
public:
Derived(int some_data): some_data(some_data) {}
virtual std::string getType() const {
return "Derived";
}
int getData() const {
return this->some_data;
}
};
class DerivedTwo : public Base {
protected:
double some_data;
public:
DerivedTwo(double some_data): some_data(some_data) {}
virtual std::string getType() const {
return "DerivedTwo";
}
// The type of data is not always the same.
double getData() const {
return this->some_data;
}
};
// The type of member should ALWAYS be Derived but then i can't store it in <callback>
void onDerivedEvent(Base& member) {
std::cout << member.getType() << std::endl;
// This is obviously not possible with member being a base class object
// member.getData();
}
// The type of member should ALWAYS be DerivedTwo but then i can't store it in <callback>
void onDerivedTwoEvent(Base& member) {
std::cout << member.getType() << std::endl;
}
int main() {
std::function<void(Base&)> callback;
callback = std::bind(onDerivedEvent, std::placeholders::_1);
callback(Derived(2));
callback = std::bind(onDerivedTwoEvent, std::placeholders::_1);
callback(DerivedTwo(3.0));
return 0;
}
我唯一想改变的是onCallback()
应该将派生类成员作为参数而不是对基对象的引用,所以我可以调用getData()
例如。
在本例中,这意味着:
void onCallback(Derived& derived);
但是,如果我这样做,我将无法再bind()
使用该方法,callback
因为参数类型不匹配。
有谁知道如何使这项工作?
// 编辑
对不起,这里的混乱,我用一些更多的细节和例子更新了源代码,以澄清我在这里做什么。
注意:
因为这看起来非常相关,所以这里是我在这里尝试做的具体用例:
它是我正在构建的引擎的事件系统的一部分。有预定义的基本事件,但它应该可以由使用此引擎的用户使用更具体的事件进行扩展。所以没有明确的派生类列表。然后某个对象可以订阅特定的事件类型,并且每当中央事件总线接收到这样的事件时,它都会以事件作为参数调用所有订阅的回调函数。我没有在派生类中添加一个和所有句柄函数的原因是,事件可以以多种方式使用。
对评论中的一些问题的回答:
如果您将 onCallback 传递给不是特定的 Derived& 的对象,会发生什么?(即,添加一个具有 doStuff2 的 Derived2。将其传递给回调。你想发生什么?那应该是不可能的。
我可能没有对此进行澄清,并且在开始时也有误导性信息,从那时起我就对其进行了编辑。传递的派生类的类型总是事先知道的。例如:onKeyEvent
将始终接收 KeyEvent 对象,而不是基类对象或任何其他派生变体。
但是,此函数绑定到的变量应该能够存储接受不同派生类的函数Base
这是我对所有事件的存储:
std::map<EventType, std::list<std::function<void(const Event&)>>> listener_map;
为什么 onCallback 不是 Derived 覆盖的 Base 方法
我在评论中回答了这个问题。...The reason i am not adding a one and for all handle function in the derived class is, the events an be used in multiple ways...
意思是,我可能有KeyEvent
一个键(哪个键,它是按下/释放/保持)的数据,并且监听功能可以将这些数据用于任何它想要的。(检查是否按下了某个特定键,检查是否按下了任何随机键等等。)其他一些事件可能根本没有任何数据,只是通知侦听器发生了某些事情或有多组数据等。
在您的代码中的任何一点,是否存在或是否存在一个有限的、有界的、在编译时从 Base 派生的所有类型的中央列表?
理论上是的。在编译期间将有有限数量的派生类。但是,对于库的编译和使用此库的项目的编译,这些可能会有所不同。
解决方案
template<class Base>
struct poly_callback {
template<class T>
static poly_callback make( std::function<void(T&)> f ) {
return { std::function<void(void*)>( [f]( void* ptr ) { f(*static_cast<T*>(static_cast<Base*>(ptr))); }) };
}
template<class T>
poly_callback( void(*pf)(T&) ):poly_callback( make<T>( pf ) ) {}
poly_callback( poly_callback const& ) = default;
poly_callback( poly_callback && ) = default;
void operator()( Base& b ) {
return type_erased( static_cast<void*>(std::addressof(b)) );
}
private:
std::function<void(void*)> type_erased;
poly_callback( std::function<void(void*)> t ):type_erased(std::move(t)) {}
};
Apoly_callback<Event>
可以存储具有与 兼容的签名的可调用对象void(Derived&)
,其中Derived
派生自Event
。当它盲目地向下转换时,必须使用类型的确切实例Derived&
或未定义的行为结果来调用它。
停止使用std::bind
,它在功能上已过时。
class Base {
public:
virtual std::string getType() const = 0;
};
class Derived : public Base {
protected:
int some_data;
public:
Derived(int some_data): some_data(some_data) {}
virtual std::string getType() const {
return "Derived";
}
int getData() const {
return this->some_data;
}
};
class DerivedTwo : public Base {
protected:
double some_data;
public:
DerivedTwo(double some_data): some_data(some_data) {}
virtual std::string getType() const {
return "DerivedTwo";
}
// The type of data is not always the same.
double getData() const {
return this->some_data;
}
};
// The type of member should ALWAYS be Derived but then i can't store it in <callback>
void onDerivedEvent(Derived& member) {
std::cout << member.getType() << "\n";
std::cout << member.getData() << "\n";
}
// The type of member should ALWAYS be DerivedTwo but then i can't store it in <callback>
void onDerivedTwoEvent(DerivedTwo& member) {
std::cout << member.getType() << "\n";
std::cout << member.getData() << "\n";
}
struct callbacks {
std::unordered_map< std::string, std::vector< poly_callback<Base> > > events;
void invoke( std::string const& name, Base& item ) {
auto it = events.find(name);
if (it == events.end())
return;
for (auto&& f : it->second)
f( item );
}
template<class Derived>
void connect( std::string const& name, void(*pf)(Derived&) )
{
events[name].push_back( pf );
}
template<class Derived>
void connect_T( std::string const& name, std::function<void(Derived&)> f )
{
events[name].push_back( std::move(f) );
}
};
int main() {
callbacks cb;
cb.connect("one", onDerivedEvent );
cb.connect("two", onDerivedTwoEvent );
Derived d(7);
DerivedTwo d2(3.14);
cb.invoke( "one", d );
cb.invoke( "two", d2 );
return 0;
}
可以针对安全性和可用性进行调整。例如,检查 typeid 是否实际匹配。
输出是:
Derived
7
DerivedTwo
3.14
如您所见,回调函数采用Derived&
和DerivedTwo&
对象。
以我的经验,这是一个糟糕的计划。
相反,broadcaster<KeyboardEvent> keyboard;
请不要使用字符串查找您的事件注册表系统。
仅当有某种方法可以统一处理回调时,从字符串到回调的映射才有意义。而且您不想统一对待这些回调。即使您为了效率而选择统一存储它们(在可笑的巨大框架中很有用),我也想要类型安全的 API 而不是映射。
推荐阅读
- java - Java:如何使用静态部分创建“变量”字段?
- react-native - 适用于 Android 和 iOS 的 React Native 中的 RSA 加密
- javascript - 为什么与完全没有 JavaScript 相比,通过 AJAX 的 GET 请求替换 div 会导致我的网站变慢这么多?
- c# - 如何在 .NET 平台上应用 EventLog
- python - 关于无法使用多个数组索引分配 numpy 数组元素的困惑
- go - Go、FreeBSD、iovec 和系统调用
- java - 有什么建议或教导可以提高这个简单程序的效率吗?
- deno - 如何从服务器流式传输异步字符串
- python - Python如何对写入数据的文件中的数据进行后处理?
- python - Django 管理面板没有样式