我已经做了大约20年的C / C ++开发人员,但模板对我来说一直是个弱点。随着模板编程变得越来越有用和复杂,在C ++ 11和C ++ 14标准中,我决定尝试练习来学习。我已经取得了一定的成功,但我遇到了一个问题,我遇到了问题。我有以下课程:
namespace Events {
// Place your new EventManager events here
static const uint32_t StatsData = 0;
static const uint32_t StatsRequest = 1;
static const uint32_t StatsReply = 2;
static const uint32_t ApplianceStatsRequest = 3;
static const uint32_t ApplianceStatsReply = 4;
static const uint32_t NullEvent = 5;
};
class EventManager {
public:
static EventManager *instance() {
if (Instance)
return Instance;
return new EventManager();
};
static void destroy() {
delete Instance;
Instance = nullptr;
}
template<typename T>
bool consume_event(uint32_t event, std::function<T> func) {
if (_event_map.find(event) == _event_map.end())
// Create the signal, in true RAII style
_event_map[event] = new boost::signals2::signal<T>();
boost::any_cast<boost::signals2::signal<T> *>(_event_map[event])->connect(func);
return true;
}
void emit(uint32_t event) {
if (_event_map.find(event) == _event_map.end())
return;
try {
boost::signals2::signal<void()> *sig =
boost::any_cast<boost::signals2::signal<void()> *>(_event_map[event]);
(*sig)();
}
catch (boost::bad_any_cast &e) {
SYSLOG(ERROR) << "Caught instance of boost::bad_any_cast: " << e.what();
abort();
}
}
template<typename... Args>
void emit(uint32_t event, Args... args) {
if (_event_map.find(event) == _event_map.end())
return;
try {
boost::signals2::signal<void(Args...)> *sig =
boost::any_cast<boost::signals2::signal<void(Args...)> *>(_event_map[event]);
(*sig)(args...);
}
catch (boost::bad_any_cast &e) {
SYSLOG(ERROR) << "Caught instance of boost::bad_any_cast: " << e.what();
abort();
}
}
private:
EventManager() { Instance = this; };
~EventManager() { Instance = nullptr; };
static EventManager *Instance;
std::map<uint32_t, boost::any> _event_map;
};
这段代码可能会进入一个大型框架,加载多个模块,这些模块是linux上的动态库。想法是让一个给定的模块能够调用:
consume_event<ParamTypes><EventNumber, SomeCallack)
回调可能是带有签名void(ParamTypes)的函数,或者是带有签名void(ParamTypes)的函数的std :: bind()的结果。
然后另一个模块可以调用:
emit<ParamTypes>(EventNumber, ParamValues)
并且每个调用了consume_event的模块都会使用ParamValues调用它的处理程序。
这似乎适用于几乎所有情况,除非我将引用传递给自定义类,如下所示:
std::cout << "Sending stats data with ref: " << std::hex << ip_entry.second << std::endl;
emit<ip_stats_t &>(Events::StatsData, *ip_entry.second);
在这种情况下,连接到信号的函数接收0xa,并在尝试将其视为ip_stats_t&amp;时立即崩溃。
输出结果为:
Sending stats data with ref: 0x7fbbc4177d50 <- This is the output of the line seen above
ips addr: 0xa << this is from the function that gets called by the signal.
更新:我刚刚注意到它通过引用传递任何变量时都做了同样的事情,而不仅仅是上面的自定义类。
此外,请注意此问题中没有SSCCE,因为任何SSCCE不变都有效。直到将工作代码放入上述框架中才会出现问题。
Update2:这里真正的问题是,如何使这种设计更好。这个不仅不能正常工作,而且从语法上讲,它很糟糕。它是丑陋的,不优雅的,而且真的,没有什么好处,除了它做了我想做的事情并增加了我对模板的理解。
Update3:我现在100%确认这与我传递的数据类型无关。如果我通过引用传递任何变量,则插槽始终接收0xa作为引用的地址。这包括std :: strings,甚至是int。如果我按值传递任何变量,则该值的复制构造函数最终会接收0xa作为要复制的值的引用。只有在模块B中创建的信号中调用模块B中的插槽时才会发生这种情况。我缺少什么?
有什么想法吗? 谢谢!
答案 0 :(得分:3)
更新我提出了一个看起来更接近你想要实现的示范:
@ lk75为了好玩,这是一种以相当可扩展的方式抽象事件机制的方法,而
- 没有过于复杂
- 不要求呼叫签名在整个地方重复(现在
Traits
)- 使用真正的RAII样式(SCNR)不泄漏信号。不再使用
new
或delete
!查看 Live On Coliru 。
请注意我如何简化单身人士并将
consume_event
和emit
同时变为单行:static EventManager& instance() { static EventManager instance; return instance; }; template <EventId event, typename F> bool consume_event(F&& func) { get_slot<event>().connect(std::forward<F>(func)); return true; } template <EventId event, typename... Args> void emit(Args&&... args) { get_slot<event>()(std::forward<Args>(args)...); }
供参考:
#include <boost/any.hpp>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/signals2/signal.hpp>
#include <iostream>
#include <memory>
#include <string>
struct ip_stats_t {
std::string canary;
};
enum class EventId : uint32_t {
// Place your new EventManager events here
StatsData = 0,
StatsRequest = 1,
StatsReply = 2,
ApplianceStatsRequest = 3,
ApplianceStatsReply = 4,
NullEvent = 5, // Not implemented
};
namespace Events {
template <EventId> struct Traits;
template <> struct Traits<EventId::StatsData> { using signal_type = boost::signals2::signal<void(int)>; } ;
template <> struct Traits<EventId::StatsRequest> { using signal_type = boost::signals2::signal<void(bool, bool)>; } ;
template <> struct Traits<EventId::StatsReply> { using signal_type = boost::signals2::signal<void(std::string)>; } ;
template <> struct Traits<EventId::ApplianceStatsRequest> { using signal_type = boost::signals2::signal<void(double, ip_stats_t&)>; } ;
//template <> struct Traits<EventId::NullEvent> { using signal_type = boost::signals2::signal<void()>; } ;
template <> struct Traits<EventId::ApplianceStatsReply> : Traits<EventId::ApplianceStatsRequest> { };
}
class EventManager {
public:
static EventManager& instance() {
static EventManager instance;
return instance;
};
template <EventId event, typename F>
bool consume_event(F&& func) {
get_slot<event>().connect(std::forward<F>(func));
return true;
}
template <EventId event, typename... Args>
void emit(Args&&... args) {
get_slot<event>()(std::forward<Args>(args)...);
}
private:
template <EventId event, typename Slot = typename Events::Traits<event>::signal_type, typename SlotPtr = boost::shared_ptr<Slot> >
Slot& get_slot() {
try {
if (_event_map.find(event) == _event_map.end())
_event_map.emplace(event, boost::make_shared<Slot>());
return *boost::any_cast<SlotPtr>(_event_map[event]);
}
catch (boost::bad_any_cast const &e) {
std::cerr << "Caught instance of boost::bad_any_cast: " << e.what() << " on event #" << static_cast<uint32_t>(event) << "\n";
abort();
}
}
EventManager() = default;
std::map<EventId, boost::any> _event_map;
};
int main() {
auto& emgr = EventManager::instance();
emgr.consume_event<EventId::ApplianceStatsRequest>([](double d, ip_stats_t& v) {
std::cout << "d: " << d << ", v.canary: " << v.canary << "\n";
});
emgr.consume_event<EventId::ApplianceStatsRequest>([](double d, ip_stats_t& v) {
std::cout << "And you can register more than one\n";
});
ip_stats_t v { "This is statically checked" };
emgr.emit<EventId::ApplianceStatsRequest>(3.142f, v);
emgr.emit<EventId::StatsData>(42); // no connection, but works
emgr.consume_event<EventId::StatsData>([](int) { std::cout << "Now it's connected\n"; });
emgr.emit<EventId::StatsData>(42); // now with connection!
#if 0
emgr.emit<EventId::ApplianceStatsRequest>(); // error: no match for call to ‘(boost::signals2::signal<void(double, ip_stats_t&)>) ()’
emgr.consume_event<EventId::NullEvent>([]{}); // use of incomplete type Traits<NullEvent>
#endif
}
您似乎在使用可变参数转发时遇到了问题:
(*sig)(std::forward<Args>(args)...);
此外,只有在通过&#34;通用引用&#34;
采取参数时,转发才有意义。template<typename... Args>
void emit(uint32_t event, Args&&... args) { // NOTE!!
但是,您不依赖于参数类型推导来获得实际值类别(rvalue与左值)。并且, 正确地 (因为编译器可能永远不会得到确切的参数类型&#34;正确&#34;匹配存储的信号(使{{1}最多失败,或者充其量只调用Undefined Behaviour。)
所以在这种情况下,你应该放弃整个转发业务:
any_cast
完整的演示程序: Live On Coliru
template<typename... Args> using Sig = boost::signals2::signal<void(Args...)>;
template<typename... Args>
void emit(uint32_t event, Args... args) {
if (_event_map.find(event) == _event_map.end())
return;
try {
Sig<Args...> *sig = boost::any_cast<Sig<Args...> *>(_event_map[event]);
(*sig)(args...);
}
catch (boost::bad_any_cast &e) {
std::cerr << "Caught instance of boost::bad_any_cast: " << e.what();
abort();
}
}
答案 1 :(得分:0)
以下代码,即没有boost :: signal的Sehe修改后的代码完全解决了我的问题。似乎boost :: signals在模块边界上传递任何数据时都会出现问题。用简单的函数向量替换它在所有情况下都适用,并且反正速度更快!
enum class EventId : uint32_t {
// Place your new EventManager events here
StatsData = 0,
StatsRequest = 1,
StatsReply = 2,
ApplianceStatsRequest = 3,
ApplianceStatsReply = 4,
};
struct ip_stats_t;
namespace Events {
template <EventId> struct Traits;
template <> struct Traits<EventId::StatsData>
{ using signal_vec = std::vector<std::function<void(ip_stats_t &)>>; } ;
template <> struct Traits<EventId::StatsRequest>
{ using signal_vec = std::vector<std::function<void(std::ostream &)>>; } ;
template <> struct Traits<EventId::StatsReply>
{ using signal_vec = std::vector<std::function<void(std::string &)>>; } ;
template <> struct Traits<EventId::ApplianceStatsRequest> :
Traits<EventId::StatsRequest> {};
template <> struct Traits<EventId::ApplianceStatsReply> :
Traits<EventId::StatsReply> {};
}
class EventManager {
public:
static EventManager& instance() {
static EventManager instance;
return instance;
};
template <EventId event, typename F>
void consume_event(F&& func) {
get_slot<event>().push_back(std::forward<F>(func));
}
template <EventId event, typename... Args>
void emit(Args&&... args) {
for (auto &vi : get_slot<event>()) {
vi(std::forward<Args>(args)...);
}
}
private:
template <EventId event,
typename Slot = typename Events::Traits<event>::signal_vec,
typename SlotPtr = std::shared_ptr<Slot>>
Slot& get_slot() {
if (_event_map.find(event) == _event_map.end())
_event_map.emplace(event, std::make_shared<Slot>());
try {
return *boost::any_cast<SlotPtr>(_event_map[event]);
}
catch (boost::bad_any_cast const &e) {
std::cerr << e.what() << " on event #" << static_cast<uint32_t>(event) << "\n";
abort();
}
}
EventManager() = default;
std::map<EventId, boost::any> _event_map;
};