私は約 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 & として処理しようとするとすぐにクラッシュします。
出力は次のとおりです。
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 のスロットを呼び出す場合にのみ発生します。
何か案は?ありがとう!