使用 std::function 作为 class-member 创建回调
Creating a callback with std::function as class-member
我借助纯虚函数设计了一个简单的callback-keyListener-"Interface"。我还使用 shared_ptr 来表达所有权,并确保监听器在处理程序中始终可用。这就像一个魅力,但现在我想在 std::function 的帮助下实现 相同的功能 ,因为有了 std::function 我可以使用 lambdas/functors 而且我不需要从某些 "interface"-类.
派生
我尝试在第二个示例中实现 std::function-变体,它 似乎 可以工作,但我有两个与示例 2 相关的问题:
为什么这个例子仍然有效,尽管侦听器超出范围? (看来,我们正在使用 copy 监听器而不是 origin 监听器?)
如何修改第二个示例,以实现与第一个示例相同的功能(在 origin 侦听器上工作)? (指向 std::function 的成员指针似乎不起作用!我们如何处理这种情况,当侦听器超出范围 在 处理程序之前?)
示例1:使用虚函数
#include <memory>
struct KeyListenerInterface
{
virtual ~KeyListenerInterface(){}
virtual void keyPressed(int k) = 0;
};
struct KeyListenerA : public KeyListenerInterface
{
void virtual keyPressed(int k) override {}
};
struct KeyHandler
{
std::shared_ptr<KeyListenerInterface> m_sptrkeyListener;
void registerKeyListener(std::shared_ptr<KeyListenerInterface> sptrkeyListener)
{
m_sptrkeyListener = sptrkeyListener;
}
void pressKey() { m_sptrkeyListener->keyPressed(42); }
};
int main()
{
KeyHandler oKeyHandler;
{
auto sptrKeyListener = std::make_shared<KeyListenerA>();
oKeyHandler.registerKeyListener(sptrKeyListener);
}
oKeyHandler.pressKey();
}
示例 2:使用 std::function
#include <functional>
#include <memory>
struct KeyListenerA
{
void operator()(int k) {}
};
struct KeyHandler
{
std::function<void(int)> m_funcKeyListener;
void registerKeyListener(const std::function<void(int)> &funcKeyListener)
{
m_funcKeyListener = funcKeyListener;
}
void pressKey() { m_funcKeyListener(42); }
};
int main()
{
KeyHandler oKeyHandler;
{
KeyListenerA keyListener;
oKeyHandler.registerKeyListener(keyListener);
}
oKeyHandler.pressKey();
}
std::function<Sig>
实现值语义回调。
这意味着它会复制您放入其中的内容。
在 C++ 中,可以复制或移动的内容应该与原始内容非常相似。您正在复制或移动的东西可以带有对外部资源的引用或指针,一切都应该正常工作。
如何准确地适应值语义取决于您希望 KeyListener 处于何种状态;在你的情况下,没有状态,没有状态的副本都是一样的。
我假设我们想要关心它存储的状态:
struct KeyListenerA {
int* last_pressed = 0;
void operator()(int k) {if (last_pressed) *last_pressed = k;}
};
struct KeyHandler {
std::function<void(int)> m_funcKeyListener;
void registerKeyListener(std::function<void(int)> funcKeyListener) {
m_funcKeyListener = std::move(funcKeyListener);
}
void pressKey() { m_funcKeyListener(42); }
};
int main() {
KeyHandler oKeyHandler;
int last_pressed = -1;
{
KeyListenerA keyListener{&last_pressed};
oKeyHandler.registerKeyListener(keyListener);
}
oKeyHandler.pressKey();
std::cout << last_pressed << "\n"; // prints 42
}
或
{
oKeyHandler.registerKeyListener([&last_pressed](int k){last_pressed=k;});
}
这里我们存储一个指向可调用状态的引用或指针。这会被复制,并且在调用时会发生正确的操作。
我与听众的问题是双寿命问题;仅当广播者和接收者都存在时,监听器 link 才有效。
为此,我使用了这样的东西:
using token = std::shared_ptr<void>;
template<class...Message>
struct broadcaster {
using reciever = std::function< void(Message...) >;
token attach( reciever r ) {
return attach(std::make_shared<reciever>(std::move(r)));
}
token attach( std::shared_ptr<reciever> r ) {
auto l = lock();
targets.push_back(r);
return r;
}
void operator()( Message... msg ) {
decltype(targets) tmp;
{
auto l = lock();
targets.erase(
std::remove_if( begin(targets), end(targets),
[](auto&& ptr){ return !(bool)ptr; }
),
end(targets)
);
tmp = targets;
}
for (auto&& f:tmp) {
if (f) f(msg...);
}
}
private:
std::mutex m;
auto lock() { return std::unique_lock<std::mutex>(m); }
std::vector< std::weak_ptr<reciever> > targets;
};
将您的代码转换为:
struct KeyHandler {
broadcaster<int> KeyPressed;
};
int main() {
KeyHandler oKeyHandler;
int last_pressed = -1;
token listen;
{
listen = oKeyHandler.KeyPressed.attach([&last_pressed](int k){last_pressed=k;});
}
oKeyHandler.KeyPressed(42);
std::cout << last_pressed << "\n"; // prints 42
listen = {}; // detach
oKeyHandler.KeyPressed(13);
std::cout << last_pressed << "\n"; // still prints 42
}
我借助纯虚函数设计了一个简单的callback-keyListener-"Interface"。我还使用 shared_ptr 来表达所有权,并确保监听器在处理程序中始终可用。这就像一个魅力,但现在我想在 std::function 的帮助下实现 相同的功能 ,因为有了 std::function 我可以使用 lambdas/functors 而且我不需要从某些 "interface"-类.
派生我尝试在第二个示例中实现 std::function-变体,它 似乎 可以工作,但我有两个与示例 2 相关的问题:
为什么这个例子仍然有效,尽管侦听器超出范围? (看来,我们正在使用 copy 监听器而不是 origin 监听器?)
如何修改第二个示例,以实现与第一个示例相同的功能(在 origin 侦听器上工作)? (指向 std::function 的成员指针似乎不起作用!我们如何处理这种情况,当侦听器超出范围 在 处理程序之前?)
示例1:使用虚函数
#include <memory>
struct KeyListenerInterface
{
virtual ~KeyListenerInterface(){}
virtual void keyPressed(int k) = 0;
};
struct KeyListenerA : public KeyListenerInterface
{
void virtual keyPressed(int k) override {}
};
struct KeyHandler
{
std::shared_ptr<KeyListenerInterface> m_sptrkeyListener;
void registerKeyListener(std::shared_ptr<KeyListenerInterface> sptrkeyListener)
{
m_sptrkeyListener = sptrkeyListener;
}
void pressKey() { m_sptrkeyListener->keyPressed(42); }
};
int main()
{
KeyHandler oKeyHandler;
{
auto sptrKeyListener = std::make_shared<KeyListenerA>();
oKeyHandler.registerKeyListener(sptrKeyListener);
}
oKeyHandler.pressKey();
}
示例 2:使用 std::function
#include <functional>
#include <memory>
struct KeyListenerA
{
void operator()(int k) {}
};
struct KeyHandler
{
std::function<void(int)> m_funcKeyListener;
void registerKeyListener(const std::function<void(int)> &funcKeyListener)
{
m_funcKeyListener = funcKeyListener;
}
void pressKey() { m_funcKeyListener(42); }
};
int main()
{
KeyHandler oKeyHandler;
{
KeyListenerA keyListener;
oKeyHandler.registerKeyListener(keyListener);
}
oKeyHandler.pressKey();
}
std::function<Sig>
实现值语义回调。
这意味着它会复制您放入其中的内容。
在 C++ 中,可以复制或移动的内容应该与原始内容非常相似。您正在复制或移动的东西可以带有对外部资源的引用或指针,一切都应该正常工作。
如何准确地适应值语义取决于您希望 KeyListener 处于何种状态;在你的情况下,没有状态,没有状态的副本都是一样的。
我假设我们想要关心它存储的状态:
struct KeyListenerA {
int* last_pressed = 0;
void operator()(int k) {if (last_pressed) *last_pressed = k;}
};
struct KeyHandler {
std::function<void(int)> m_funcKeyListener;
void registerKeyListener(std::function<void(int)> funcKeyListener) {
m_funcKeyListener = std::move(funcKeyListener);
}
void pressKey() { m_funcKeyListener(42); }
};
int main() {
KeyHandler oKeyHandler;
int last_pressed = -1;
{
KeyListenerA keyListener{&last_pressed};
oKeyHandler.registerKeyListener(keyListener);
}
oKeyHandler.pressKey();
std::cout << last_pressed << "\n"; // prints 42
}
或
{
oKeyHandler.registerKeyListener([&last_pressed](int k){last_pressed=k;});
}
这里我们存储一个指向可调用状态的引用或指针。这会被复制,并且在调用时会发生正确的操作。
我与听众的问题是双寿命问题;仅当广播者和接收者都存在时,监听器 link 才有效。
为此,我使用了这样的东西:
using token = std::shared_ptr<void>;
template<class...Message>
struct broadcaster {
using reciever = std::function< void(Message...) >;
token attach( reciever r ) {
return attach(std::make_shared<reciever>(std::move(r)));
}
token attach( std::shared_ptr<reciever> r ) {
auto l = lock();
targets.push_back(r);
return r;
}
void operator()( Message... msg ) {
decltype(targets) tmp;
{
auto l = lock();
targets.erase(
std::remove_if( begin(targets), end(targets),
[](auto&& ptr){ return !(bool)ptr; }
),
end(targets)
);
tmp = targets;
}
for (auto&& f:tmp) {
if (f) f(msg...);
}
}
private:
std::mutex m;
auto lock() { return std::unique_lock<std::mutex>(m); }
std::vector< std::weak_ptr<reciever> > targets;
};
将您的代码转换为:
struct KeyHandler {
broadcaster<int> KeyPressed;
};
int main() {
KeyHandler oKeyHandler;
int last_pressed = -1;
token listen;
{
listen = oKeyHandler.KeyPressed.attach([&last_pressed](int k){last_pressed=k;});
}
oKeyHandler.KeyPressed(42);
std::cout << last_pressed << "\n"; // prints 42
listen = {}; // detach
oKeyHandler.KeyPressed(13);
std::cout << last_pressed << "\n"; // still prints 42
}