【发布时间】:2014-10-13 18:18:51
【问题描述】:
我从事 C/C++ 开发人员已有大约 20 年了,但模板一直是我的弱点。随着模板编程在 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) 的函数,或者是 std::bind() 对带有签名 void(ParamTypes) 的函数的结果。
然后另一个模块可以调用:
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 & 时立即崩溃。
输出是:
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% 确认这与我传递的数据类型无关。如果我通过引用传递 any 变量,则插槽始终接收 0xa 作为引用的地址。这包括 std::strings,甚至 int。如果我按值传递任何变量,则该值的复制构造函数最终会接收 0xa 作为要从中复制的值的引用。这只发生在从模块 A 中创建的信号调用模块 B 中的插槽时。我错过了什么?
有什么想法吗? 谢谢!
【问题讨论】:
-
未显示:检查
ip_entry.second是否不是过时的指针(例如,指向超出范围的堆栈对象或已删除的堆对象) -
谢谢,它肯定不是一个过时的指针。这些是存储在映射中的指针,在包含它的整个类被删除之前不会被删除。为了确保,我通过 GDB 检查了数据,它是有效的。
-
很高兴把它排除在外!
-
在您添加的代码中,您仍然没有显示相关的移动部件。对于失败的案例,您如何调用
consume_event<>和emit<>?请为此制作一个SSCCE。你很快就会发现问题。 (另外,您是否跨动态模块边界调用?) -
感谢您的关注,但我认为问题stackoverflow.com/questions/7957239/… 对我正在尝试做的事情有一些有趣的可能性。它似乎使用单个 void() 信号并使用 std::bind() 传递参数来实现您之前建议的内容。我要试一试。感谢您的意见!