C++中ID与通用回调映射的可行性咨询及实现方案
Your core idea of mapping IDs to callbacks with different signatures is absolutely feasible—we just need to fix the type homogeneity issue in the map and correct the template usage. Let's break down the problems in your code and walk through two solid solutions.
Why Your Current Code Fails
There are two critical issues:
- Missing Template Arguments:
Callbackis a template class, sostd::map<int, Callback>is invalid—you must specify the template parameters (e.g.,Callback<bool, int>). - Type Mismatch in the Map: A
std::maprequires all values to be the same type. SinceClass1::runtakes anintandClass2::runtakes astd::string, their correspondingCallbackinstantiations (Callback<bool, int>andCallback<bool, std::string>) are distinct types that can't coexist directly in the same map.
We solve this with type erasure—a way to store objects of different types in a homogeneous container.
Solution 1: Using std::any (Simplest Approach)
std::any (C++17+) lets you store any type in a single container. We'll use it to hold our different Callback instances, then cast back to the correct type when invoking the callback.
Corrected Code
#include <iostream> #include <map> #include <functional> #include <any> #include <string> class Class1 { public: bool run(const int &temp){ std::cout << "worker1:" << temp << std::endl; return false; // Using false instead of 0 for clarity } }; class Class2 { public: bool run(const std::string &temp){ std::cout << "worker2:" << temp << std::endl; return false; } }; template <typename ReturnType, typename... Args> class Callback { private: std::function<ReturnType(Args...)> _function; public: Callback(std::function<ReturnType(Args...)> function) : _function(std::move(function)) {} ReturnType run(Args... args) { return _function(std::forward<Args>(args)...); } }; int main() { Class1 class1; Class2 class2; std::map<int, std::any> cbs; // Store callbacks with their respective types cbs[1] = Callback<bool, int>(std::bind(&Class1::run, &class1, std::placeholders::_1)); // Alternatively, use a lambda for readability: // cbs[2] = Callback<bool, std::string>([&class2](const std::string& temp) { return class2.run(temp); }); cbs[2] = Callback<bool, std::string>(std::bind(&Class2::run, &class2, std::placeholders::_1)); // Safely retrieve and invoke callbacks if (auto* cb = std::any_cast<Callback<bool, int>>(&cbs[1])) { cb->run(1); } if (auto* cb = std::any_cast<Callback<bool, std::string>>(&cbs[2])) { cb->run("string msg"); } return 0; }
Key Notes:
- We use
std::any_castwith a pointer to avoid exceptions if the type doesn't match (returnsnullptrinstead). - Lambdas are often more readable than
std::bind—I included both options for flexibility.
Solution 2: Polymorphic Base Class (More Flexible)
If you need more control over callback behavior (e.g., adding common methods to all callbacks), use a polymorphic base class with dynamic casting.
Corrected Code
#include <iostream> #include <map> #include <functional> #include <memory> #include <string> class CallbackBase { public: virtual ~CallbackBase() = default; // Required for proper cleanup of derived objects }; template <typename ReturnType, typename... Args> class Callback : public CallbackBase { private: std::function<ReturnType(Args...)> _function; public: Callback(std::function<ReturnType(Args...)> function) : _function(std::move(function)) {} ReturnType run(Args... args) { return _function(std::forward<Args>(args)...); } }; class Class1 { public: bool run(const int &temp){ std::cout << "worker1:" << temp << std::endl; return false; } }; class Class2 { public: bool run(const std::string &temp){ std::cout << "worker2:" << temp << std::endl; return false; } }; int main() { Class1 class1; Class2 class2; std::map<int, std::unique_ptr<CallbackBase>> cbs; // Store callbacks as unique_ptr to the base class cbs[1] = std::make_unique<Callback<bool, int>>(std::bind(&Class1::run, &class1, std::placeholders::_1)); cbs[2] = std::make_unique<Callback<bool, std::string>>([&class2](const std::string& temp) { return class2.run(temp); }); // Dynamic cast to the correct derived type and invoke if (auto* cb = dynamic_cast<Callback<bool, int>*>(cbs[1].get())) { cb->run(1); } if (auto* cb = dynamic_cast<Callback<bool, std::string>*>(cbs[2].get())) { cb->run("string msg"); } return 0; }
Key Notes:
CallbackBaseprovides a common interface for all callbacks, andstd::unique_ptrmanages memory safely to avoid leaks.dynamic_castchecks if the stored object matches the expected type before invoking, preventing undefined behavior.
Both solutions work well—choose the one that fits your needs. The std::any approach is simpler for most cases, while the polymorphic approach is better if you plan to extend callback functionality later.
内容的提问来源于stack exchange,提问作者GiGa

