我想实现一个管理器,它使用C ++ 11将回调存储到多态类的成员函数中。问题是我不知道如何处理成员所属对象被删除或应该被删除的情况,我想让界面尽可能简单。
所以我想到了以下内容:向对象存储std::weak_ptr
以及向成员存储std::function
。
以下似乎有效:
class MyBase {
public:
MyBase() {}
virtual ~MyBase() {}
};
//--------------------------------------------------
class MyClass : public MyBase {
public:
MyClass() : MyBase() {}
void myDouble(double val) const { std::cout << "Value is: " << val << std::endl; }
};
//--------------------------------------------------
Class Manager {
public:
void setFunction(std::weak_ptr<MyBase> base, std::function<void(double)> func) {
m_function.first = base;
m_function.second = func;
}
private:
std::pair<std::weak_ptr<MyBase>, std::function<void(double)>> m_function;
};
要使用它:
Manager db;
std::shared_ptr<MyClass> myClass = std::make_shared<MyClass>();
db.setFunction(myClass, std::bind(&MyClass::myDouble, myClass, std::placeholders::_1));
现在我想隐藏用户的std::bind
部分,这样他只需要打电话:
db.setFunction(myClass, &MyClass::myDouble);
所以我想在我的经理职能部门中完成以下工作:
void setFunc2(std::weak_ptr<MyBase> base, std::function<void(double)> func) {
m_function.first = base;
m_function.second = std::bind(func, base, std::placeholders::_1);
}
但上面给出了错误:
error: no match for 'operator=' (operand types are 'std::function<void(double)>' and
'std::_Bind_helper<false, std::function<void(double)>&, std::weak_ptr<MyBase>&, const std::_Placeholder<1>&>::type {aka std::_Bind<std::function<void(double)>(std::weak_ptr<MyBase>, std::_Placeholder<1>)>}')
m_function.second = std::bind(func, base, std::placeholders::_1);
有没有更好的方法来实现这一目标,或者可能是一种让这种方式发挥作用的方法?
我注意到一些有趣的东西。如果我使用std::shared_ptr
use_count()
,则会在原始代码中调用std::bind
来增加{{1}}。因此我无法手动重置/销毁对象,除非我取消了我的经理上的成员。这种行为记录在哪里,我通常使用cppreference?
我查看过以下问题,但似乎无法解决我的问题:How can I use polymorphism with std::function?
答案 0 :(得分:7)
模板setFunction
,以便您可以接受指向成员的指针,并且不必为cv / ref限定符的组合编写12次重载。
template<class D, class D2, class F>
void setFunction(const std::shared_ptr<D> &sp, F D2::* member) {
// optionally static_assert that D2 is a base of D.
m_function.first = sp;
m_function.second = std::bind(member, sp.get(), std::placeholders::_1);
}
显然,在致电lock()
之前,您需要确保m_function.first
m_function.second
。
或者,只需使用捕获weak_ptr
和成员函数指针的lambda:
std::function<void(double)> m_function;
template<class D, class D2, class F>
void setFunction(const std::shared_ptr<D> &sp, F D2::* member) {
std::weak_ptr<D> wp = sp;
m_function = [wp, member](double d) {
if(auto sp = wp.lock()){
((*sp).*member)(d);
}
else {
// handle pointer no longer valid case.
}
};
}
答案 1 :(得分:2)
我喜欢将我的侦听器/广播器与侦听器的实现分离。
这意味着我无法对听众提出要求。它不能要求以特定方式分配监听器。
我发现的最简单的方法是让广播公司返回一个令牌,其生命周期决定了连接的生命周期。
using token = std::shared_ptr<void>;
template<class...Args>
struct broadcaster {
using target = std::function<void(Args...)>;
using wp_target = std::weak_ptr<target>;
using sp_target = std::shared_ptr<target>;
static sp_target wrap_target( target t ) {
return std::make_shared<target>(std::move(t));
};
token start_to_listen( target f ) {
auto t = wrap_target(std::move(f));
targets.push_back(t);
return t;
}
void broadcast( Args... args ) {
targets.erase(
std::remove_if( targets.begin(), targets.end(),
[&]( wp_target t )->bool { return t.lock(); }
),
targets.end()
);
auto targets_copy = targets; // in case targets is modified by listeners
for (auto wp : targets_copy) {
if (auto sp = wp.lock()) {
(*sp)(args...);
}
}
}
std::vector<wp_target> targets;
};
这会迫使注册听众的人保持std::shared_ptr<void>
。
我们甚至可以让它变得更加漂亮,最后shared_ptr<void>
的破坏实际上会立即从列表中删除侦听器。但是上述懒惰的注销似乎在我的经验中运作得相当好,并且使其多线程友好相对容易。 (一个严重的问题是,当广播事件移除或添加到侦听器列表中时会发生什么:使用上述内容使其工作是很好的,并且使用侦听器在广播时不添加广播时添加的规则,以及在广播不接收广播。在广播期间,同时移除的听众可以在我的大部分实现中获得广播......这样做会避免昂贵。)
我们可以改为以不同方式解耦。听众可以分别将std::function
和std::weak_ptr
传递给广播公司,广播公司会存储两者,只有在std::function
有效时才会调用std::weak_ptr
。
答案 2 :(得分:1)
我喜欢Yakk的方法。这是一个更新版本,修复了一些编译问题(例如,无法命名功能&#39;注册&#39;)。它还为客户端添加了一个rm_callback方法,可以轻松删除他们的注册,而不会强制他们的注册令牌超出范围或了解内部。我不想在每次广播事件时扫描列表,所以我在共享指针上添加了一个删除器来执行清理任务。引入的所有新错误或效率低下都是我的。警报阅读器在广播时修改列表时应该注意线程问题...
using token = std::shared_ptr<void>;
template<class...Args>
struct broadcaster {
using target = std::function<void(Args...)>;
using wp_target = std::weak_ptr<target>;
using sp_target = std::shared_ptr<target>;
token add_callback(target f) {
sp_target t(new target(std::move(f)), [&](target*obj) { delete obj; cleanup(); });
targets.push_back(t);
return t;
}
static void rm_callback(token& t)
{
t.reset();
}
void cleanup()
{
targets.erase(
std::remove_if(targets.begin(), targets.end(),
[](wp_target t) { return t.expired(); }
),
targets.end()
);
}
void broadcast(Args... args) {
for (auto wp : targets) {
if (auto sp = wp.lock()) {
(*sp)(args...);
}
}
}
std::vector<wp_target> targets;
};
// declare event taking a string arg
broadcaster<std::string> myEvent;