C++模板可调用对象缓存器的语法、约束与运行时问题
策略缓存器(StrategyCacher)问题解答
问题1:简化带模板参数的KeyGen调用写法
现有写法KeyGen{}.template operator()<ConcreteStrategy>(args...)确实冗长,有两种优化方式:
- 封装辅助函数:在
StrategyCacher内部添加私有模板函数统一处理调用逻辑:
后续调用只需写template<typename ConcreteStrategy, typename... Args> std::size_t generate_key(Args&&... args) { return KeyGen{}.template operator()<ConcreteStrategy>(std::forward<Args>(args)...); }generate_key<ConcreteStrategy>(args...)即可。 - 调整KeyGen设计:将策略类型作为
KeyGen的模板参数传入,而非operator()的模板参数,这样operator()就无需带模板参数,直接调用KeyGen<ConcreteStrategy>{}(args...)即可。
问题2:为KeyGen添加返回std::size_t的约束
C++20及以上(推荐用Concepts)
先定义约束Concept,再在模板类中应用:
template<typename KeyGen, typename Strategy, typename... Args> concept ValidKeyGenerator = requires(KeyGen kg, Args&&... args) { // 约束KeyGen的模板operator()调用后返回std::size_t { kg.template operator()<Strategy>(std::forward<Args>(args)...) } -> std::same_as<std::size_t>; }; // 在StrategyCacher模板参数中添加约束 template<typename BaseStrategy, typename KeyGen> class StrategyCacher { public: template<typename ConcreteStrategy, typename... Args> requires ValidKeyGenerator<KeyGen, ConcreteStrategy, Args...> auto get(Args&&... args) { // 业务逻辑 } };
C++17及以下(用static_assert+类型特性)
利用std::is_invocable_r做编译期检查:
template<typename BaseStrategy, typename KeyGen> class StrategyCacher { public: template<typename ConcreteStrategy, typename... Args> auto get(Args&&... args) { static_assert(std::is_invocable_r_v<std::size_t, decltype(&KeyGen::template operator()<ConcreteStrategy>), KeyGen, Args...>, "KeyGen必须返回std::size_t类型"); // 业务逻辑 } };
问题3:修复程序退出时的对象销毁运行时错误
这类错误几乎都和对象生命周期管理不当有关,按以下步骤排查修复:
- 检查缓存存储类型:避免用裸指针存储策略对象,改用
std::shared_ptr<BaseStrategy>,由智能指针自动管理对象生命周期,防止重复释放或悬空指针:std::unordered_map<std::size_t, std::shared_ptr<BaseStrategy>> cache_; // 存入缓存时用make_shared构造 auto strategy = std::make_shared<ConcreteStrategy>(std::forward<Args>(args)...); cache_[key] = strategy; - 验证键生成的唯一性:如果相同参数+策略类型生成重复键,会导致缓存对象被覆盖,旧对象可能被提前销毁但外部仍持有引用。务必确保键生成逻辑能唯一标识每个不同的构造参数组合。
- 检查缓存生命周期:若缓存是全局对象,需确保其销毁晚于所有依赖的策略对象,或改用
std::weak_ptr配合shared_ptr,避免资源已销毁时触发析构错误。
问题4:更简洁灵活的整体实现方案
可以抛弃模板式KeyGen,改用std::function接收任意可调用对象作为键生成器,结合智能指针简化缓存逻辑,示例如下:
#include <unordered_map> #include <memory> #include <functional> #include <typeinfo> #include <vector> #include <cstring> #include <boost/functional/hash.hpp> // 用于组合哈希值 template<typename BaseStrategy> class StrategyCacher { private: // 键生成器:接收策略类型信息和参数序列化字节流,返回size_t using KeyGenerator = std::function<std::size_t(const std::type_info&, const std::vector<std::byte>&)>; KeyGenerator key_gen_; std::unordered_map<std::size_t, std::shared_ptr<BaseStrategy>> cache_; // 辅助函数:组合参数哈希值替代序列化(更简洁) template<typename... Args> std::size_t combine_hashes(Args&&... args) { std::size_t hash = 0; (boost::hash_combine(hash, std::hash<std::decay_t<Args>>{}(args)), ...); return hash; } public: // 构造时传入自定义键生成逻辑 explicit StrategyCacher(KeyGenerator key_gen) : key_gen_(std::move(key_gen)) {} template<typename ConcreteStrategy, typename... Args> std::shared_ptr<BaseStrategy> get(Args&&... args) { std::size_t param_hash = combine_hashes(std::forward<Args>(args)...); std::size_t key = key_gen_(typeid(ConcreteStrategy), std::vector<std::byte>(reinterpret_cast<std::byte*>(¶m_hash), reinterpret_cast<std::byte*>(¶m_hash) + sizeof(param_hash))); if (auto it = cache_.find(key); it != cache_.end()) { return it->second; } auto strategy = std::make_shared<ConcreteStrategy>(std::forward<Args>(args)...); cache_[key] = strategy; return strategy; } }; // 使用示例 int main() { // 自定义键生成逻辑:组合策略类型哈希和参数哈希 auto cacher = StrategyCacher<GenerationStrategy>([](const std::type_info& ti, const std::vector<std::byte>& param_data) { std::size_t param_hash; std::memcpy(¶m_hash, param_data.data(), sizeof(param_hash)); std::size_t key = ti.hash_code(); boost::hash_combine(key, param_hash); return key; }); // 获取缓存的策略对象 auto strategy = cacher.get<ConcreteStrategy>(100, "test"); return 0; }
该方案的优势:
- 无需处理模板operator()的繁琐语法,键生成逻辑可灵活替换(lambda、函数对象、普通函数均可)
- 用
shared_ptr彻底解决生命周期问题 - 哈希组合逻辑简洁通用,无需复杂的参数序列化
内容的提问来源于stack exchange,提问作者Gerrard
相关产品推荐
相关产品推荐

