如何运行时实例化带SinkType模板参数的Logger类并保留Concept约束
保留模板类Concept约束的运行时实例化方案
核心思路是用std::variant存储模板Logger的不同实例,配合模板转发保留Concept约束,同时通过工厂方法实现运行时根据配置选择SinkType。
步骤1:定义基础组件
首先定义你的StringLike Concept和Sink类型:
#include <variant> #include <string> #include <iostream> #include <fstream> #include <nlohmann/json.hpp> template<typename T> concept StringLike = requires(T t) { { std::string(t) } -> std::convertible_to<std::string>; }; // 文件Sink struct FileSink { void Output(StringLike auto&& msg) { std::ofstream file("log.txt", std::ios::app); file << msg << '\n'; } }; // 控制台Sink struct ConsoleSink { void Output(StringLike auto&& msg) { std::cout << msg << '\n'; } }; // 模板Logger实现类 template<typename SinkType> class LoggerImpl { public: void Log(StringLike auto&& msg) { sink_.Output(std::forward<decltype(msg)>(msg)); } private: SinkType sink_; };
步骤2:封装运行时可选择的Logger类
用std::variant存储所有可能的LoggerImpl实例,通过工厂方法根据JSON配置创建对应实例,同时提供带StringLike约束的模板Log方法:
class Logger { public: // 从JSON配置创建Logger实例 static Logger CreateFromConfig(const nlohmann::json& config) { const std::string sink_type = config.at("sink_type"); if (sink_type == "file") { return Logger(LoggerImpl<FileSink>{}); } else if (sink_type == "console") { return Logger(LoggerImpl<ConsoleSink>{}); } throw std::invalid_argument("Unsupported sink type: " + sink_type); } // 带StringLike约束的Log方法,编译时检查参数合法性 template<StringLike T> void Log(T&& msg) { std::visit([&](auto& impl) { impl.Log(std::forward<T>(msg)); }, impl_); } private: // 存储所有可能的LoggerImpl实例 std::variant<LoggerImpl<FileSink>, LoggerImpl<ConsoleSink>> impl_; // 私有构造函数,仅由工厂方法调用 explicit Logger(decltype(impl_) impl) : impl_(std::move(impl)) {} };
步骤3:使用示例
int main() { // 模拟从JSON读取配置 nlohmann::json config = R"({"sink_type": "console"})"_json; Logger logger = Logger::CreateFromConfig(config); logger.Log("Hello, runtime sink!"); // 编译时检查是否符合StringLike logger.Log(123); // 编译报错,不符合StringLike约束,保留了原模板类的检查能力 return 0; }
方案优势
- 保留了
Log方法的StringLikeConcept约束,编译时就能拦截非法参数,不会像动态多态那样丢失编译期检查 - 通过
std::variant和工厂方法实现了运行时选择SinkType的能力,无需放弃模板类的设计 - 性能接近直接使用模板类,
std::visit在大多数情况下会被编译器优化为分支判断,开销极小
扩展场景
如果后续需要新增Sink类型,只需:
- 定义新的Sink结构体
- 将
LoggerImpl<NewSink>添加到std::variant的类型列表中 - 在
CreateFromConfig中添加对应的分支判断即可
内容的提问来源于stack exchange,提问作者user7854092
相关产品推荐
相关产品推荐

