如何使用仿函数和 operator() 实现委托
How to implement delegates using functors and operator()
我一直在尝试遵循以下一些建议:
https://softwareengineering.stackexchange.com/a/213635/46534
和
https://en.wikipedia.org/wiki/Function_object
但是很难让它编译。
我有一个委托定义:
struct SomeDelegate {
void operator()(SomeType *data) {
//do some stuff with data
}
};
然后是接受函数指针的成员函数:
void DoSomethingThatCallsback(void(*callback)(SomeType *) ) {
callback(ptrToSomeType);
}
然后当我尝试按如下方式使用此成员函数时:
foo.DoSomethingThatCallsback(SomeDelegate());
我得到编译错误:
cannot convert argument 1 from 'SomeDelegate' to 'void (__cdecl *)(Core::SomeType *)'
所有 我一直在阅读的示例表明这是可能的。
我试过使用这样的模板:
template <typename Callback>
void DoSomethingThatCallsback(Callback callback)
但是我得到了类似的错误。
我最终正在寻找一种更面向对象的方法来求助于函数指针或迁移到 C++11。另外,无法使用STL。
更新了更多上下文
struct MapOpenedDelegate {
public:
void operator()(Map *openedMap) {
}
};
class MapReader {
public:
template <typename Callback>
void RegisterMapOpenedCallback(Callback &callback) {
_callbacks.Add(callback);
}
private:
Rise::List<void(*)(Map *)> _callbacks;
};
...
mapReader.RegisterMapOpenedCallback(MapOpenedDelegate());
第一种方法行不通,因为您在向函数传递 SomeDelegate
实例时告诉函数接受函数指针。
模板方法确实可行,具体取决于模板的主体。
可用的模板示例:
template <typename Callback>
void DoSomethingThatCallsback(Callback& callback) {
callback(ptrToSomeType);
}
请注意,鉴于 ptrToSomeType
的类型为 SomeType*
,此方法有效。并且 Callback
类型等于 SomeDelegate
类型,就像您在问题中定义的那样,或者提供 operator()
接受 SomeType*
作为其唯一参数的任何其他类型.
更新
考虑到您现在提供给我的内容,我认为没有理由在此处使用模板。
首先,在我之前的回答中,我并不知道这些回调实例要存储在列表中。这彻底改变了局面。
我在下面的问题解决方案中假设 RegisterMapOpenedCallback
的调用者拥有回调实例的所有权。调用者通过引用传递它们,MapReader
只存储指向它们的指针。
struct MapOpenedDelegate {
public:
void operator()(Map *openedMap) {
}
};
class MapReader {
public:
void RegisterMapOpenedCallback(MapOpenedDelegate& callback) {
_callbacks.Add(&callback);
}
private:
Rise::List<MapOpenedDelegate*> _callbacks;
};
为了提供一个非常清晰的解决方案,我仍然缺少上下文。除了存储一些回调实例之外,我不太确定你想要实现什么。不过,我希望以上内容能对您有所帮助。
在我看来 std::function 可以解决您所有的问题:
A std::function<ReturnType(Parameters)>
能够封装任何行为类似于具有参数 Parameters
和 return 类型 ReturnType
的函数,尤其是函子、lambda 和函数指针。
因此用 std::function 替换函数指针应该可以解决问题。
编辑:如果您不被允许使用完整的标准库(STL != 标准库),您可能必须使用像
这样的薄包装器自己实现该功能
struct MyDelegate {
virtual operator()(...);
}
然后使用动态多态性为所有可能的可调用对象实现它:
template <typename Callable>
struct MyDelegateImpl : MyDelegate {
Callable m_f;
virtual operator()(...) { m_f(...); }
}
请注意,这只是一个粗略的草图,因此赋值等可能会有更多问题。确保将代表存储在 std::unique_ptr
中,以便您的对象 don't get sliced
据我从你的问题和对答案的评论中了解到,你想创建一个容器来擦除侦听器的类型,并在需要时通过回调方法调用它们。当然,您出于未知原因不想使用标准库,因此 lambda 和 std::function
超出了此处的范围。
到目前为止,还不错。它遵循基于您的示例代码的最小工作片段:
#include<vector>
struct Map {
// You haven't provided a definition for this class
};
struct ADelegate {
void operator()(Map *) {
// ...
}
};
struct AnotherDelegate {
void operator()(Map *) {
// ...
}
};
class MapReader {
using fn_type = void(*)(void*, Map *);
struct Callback {
void *ptr;
fn_type fn;
};
template<typename T>
static void proto(void *ptr, Map *map) {
(*static_cast<T *>(ptr))(map);
}
public:
template <typename T>
void attach(T *ptr) {
callbacks.push_back({ ptr, &proto<T> });
}
void operator()() {
Map map;
for(auto &&cb: callbacks) {
cb.fn(cb.ptr, &map);
}
}
private:
// I used a vector for I don't know how Rise::List is implemented
std::vector<Callback> callbacks;
};
int main() {
MapReader mr;
ADelegate d1;
AnotherDelegate d2;
mr.attach(&d1);
mr.attach(&d2);
mr();
}
我使用了 std::vector
,因为我没有 Rise::List
的实现。我相信您可以轻松地将其替换为您自己的列表类型。
请参阅上面的代码段和 wandbox 上的 运行。
让我们看看它是如何工作的。
几乎所有事情都发生在 MapReader
class 内。如果你想存储属于不同类型的回调,一个可能的解决方案是类型擦除原始类型并将它们放在 void *
框中。然后,您可以在需要时通过添加额外的间接层(静态模板函数 proto
)取回类型并进行正确的调用。
如您所见,它正常工作,您可以将不同的类型附加到 MapReader
(请注意,您必须保证侦听器的生命周期超过发射器之一)。您为此付出的代价是通过中间函数的跳转,该函数在调用之前将 void *
转换为正确的类型。
我一直在尝试遵循以下一些建议:
https://softwareengineering.stackexchange.com/a/213635/46534
和
https://en.wikipedia.org/wiki/Function_object
但是很难让它编译。
我有一个委托定义:
struct SomeDelegate {
void operator()(SomeType *data) {
//do some stuff with data
}
};
然后是接受函数指针的成员函数:
void DoSomethingThatCallsback(void(*callback)(SomeType *) ) {
callback(ptrToSomeType);
}
然后当我尝试按如下方式使用此成员函数时:
foo.DoSomethingThatCallsback(SomeDelegate());
我得到编译错误:
cannot convert argument 1 from 'SomeDelegate' to 'void (__cdecl *)(Core::SomeType *)'
所有 我一直在阅读的示例表明这是可能的。
我试过使用这样的模板:
template <typename Callback>
void DoSomethingThatCallsback(Callback callback)
但是我得到了类似的错误。
我最终正在寻找一种更面向对象的方法来求助于函数指针或迁移到 C++11。另外,无法使用STL。
更新了更多上下文
struct MapOpenedDelegate {
public:
void operator()(Map *openedMap) {
}
};
class MapReader {
public:
template <typename Callback>
void RegisterMapOpenedCallback(Callback &callback) {
_callbacks.Add(callback);
}
private:
Rise::List<void(*)(Map *)> _callbacks;
};
...
mapReader.RegisterMapOpenedCallback(MapOpenedDelegate());
第一种方法行不通,因为您在向函数传递 SomeDelegate
实例时告诉函数接受函数指针。
模板方法确实可行,具体取决于模板的主体。
可用的模板示例:
template <typename Callback>
void DoSomethingThatCallsback(Callback& callback) {
callback(ptrToSomeType);
}
请注意,鉴于 ptrToSomeType
的类型为 SomeType*
,此方法有效。并且 Callback
类型等于 SomeDelegate
类型,就像您在问题中定义的那样,或者提供 operator()
接受 SomeType*
作为其唯一参数的任何其他类型.
更新
考虑到您现在提供给我的内容,我认为没有理由在此处使用模板。
首先,在我之前的回答中,我并不知道这些回调实例要存储在列表中。这彻底改变了局面。
我在下面的问题解决方案中假设 RegisterMapOpenedCallback
的调用者拥有回调实例的所有权。调用者通过引用传递它们,MapReader
只存储指向它们的指针。
struct MapOpenedDelegate {
public:
void operator()(Map *openedMap) {
}
};
class MapReader {
public:
void RegisterMapOpenedCallback(MapOpenedDelegate& callback) {
_callbacks.Add(&callback);
}
private:
Rise::List<MapOpenedDelegate*> _callbacks;
};
为了提供一个非常清晰的解决方案,我仍然缺少上下文。除了存储一些回调实例之外,我不太确定你想要实现什么。不过,我希望以上内容能对您有所帮助。
在我看来 std::function 可以解决您所有的问题:
A std::function<ReturnType(Parameters)>
能够封装任何行为类似于具有参数 Parameters
和 return 类型 ReturnType
的函数,尤其是函子、lambda 和函数指针。
因此用 std::function 替换函数指针应该可以解决问题。
编辑:如果您不被允许使用完整的标准库(STL != 标准库),您可能必须使用像
这样的薄包装器自己实现该功能struct MyDelegate {
virtual operator()(...);
}
然后使用动态多态性为所有可能的可调用对象实现它:
template <typename Callable>
struct MyDelegateImpl : MyDelegate {
Callable m_f;
virtual operator()(...) { m_f(...); }
}
请注意,这只是一个粗略的草图,因此赋值等可能会有更多问题。确保将代表存储在 std::unique_ptr
中,以便您的对象 don't get sliced
据我从你的问题和对答案的评论中了解到,你想创建一个容器来擦除侦听器的类型,并在需要时通过回调方法调用它们。当然,您出于未知原因不想使用标准库,因此 lambda 和 std::function
超出了此处的范围。
到目前为止,还不错。它遵循基于您的示例代码的最小工作片段:
#include<vector>
struct Map {
// You haven't provided a definition for this class
};
struct ADelegate {
void operator()(Map *) {
// ...
}
};
struct AnotherDelegate {
void operator()(Map *) {
// ...
}
};
class MapReader {
using fn_type = void(*)(void*, Map *);
struct Callback {
void *ptr;
fn_type fn;
};
template<typename T>
static void proto(void *ptr, Map *map) {
(*static_cast<T *>(ptr))(map);
}
public:
template <typename T>
void attach(T *ptr) {
callbacks.push_back({ ptr, &proto<T> });
}
void operator()() {
Map map;
for(auto &&cb: callbacks) {
cb.fn(cb.ptr, &map);
}
}
private:
// I used a vector for I don't know how Rise::List is implemented
std::vector<Callback> callbacks;
};
int main() {
MapReader mr;
ADelegate d1;
AnotherDelegate d2;
mr.attach(&d1);
mr.attach(&d2);
mr();
}
我使用了 std::vector
,因为我没有 Rise::List
的实现。我相信您可以轻松地将其替换为您自己的列表类型。
请参阅上面的代码段和 wandbox 上的 运行。
让我们看看它是如何工作的。
几乎所有事情都发生在 MapReader
class 内。如果你想存储属于不同类型的回调,一个可能的解决方案是类型擦除原始类型并将它们放在 void *
框中。然后,您可以在需要时通过添加额外的间接层(静态模板函数 proto
)取回类型并进行正确的调用。
如您所见,它正常工作,您可以将不同的类型附加到 MapReader
(请注意,您必须保证侦听器的生命周期超过发射器之一)。您为此付出的代价是通过中间函数的跳转,该函数在调用之前将 void *
转换为正确的类型。