我已经做了大约 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) 的函数,或者是 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% 确认这与我传递的数据类型无关。如果我通过引用传递任何变量,则插槽始终接收 0xa 作为引用的地址。这包括 std::strings,甚至 int。如果我按值传递任何变量,则该值的复制构造函数最终会接收 0xa 作为要从中复制的值的引用。这只发生在从模块 A 中创建的信号调用模块 B 中的插槽时。我错过了什么?
有任何想法吗?谢谢!