我开发了一个库,用于其他库和应用程序。由应用程序提供实际的日志记录功能。库应仅定义一组详细级别,并在发生应记录的事情时将消息传递给应用程序。因此,我的想法是在我的库中存储一个std::function
对象,该对象实际上指向客户端应用程序中的某个回调。请注意,前面没有通过va_start, va_list
和va_end
访问的C风格可变模板参数的选项。这是因为客户端可以使用诸如spdlog
之类的非基于printf的方案作为示例。我知道没有提供MWE,但那是因为代码甚至没有编译。
库:
.hpp
enum class severity_t
{
trace = 0,
debug = 1,
warn = 2,
error = 3,
critical = 4
};
template <typename ...args_t>
using logging_callback_t = std::function<void(severity_t, std::string, args_t...)>;
template <typename ...args_t>
extern logging_callback_t<args_t...> logging_callback;
template <typename ...args_t>
void log(severity_t level, const std::string& msg, args_t&& ...args)
{
logging_callback<args_t...>(level, msg, std::forward<args_t>(args)...);
}
template <typename ...args_t>
void set_logging_cbk(logging_callback_t<args_t...> func)
{
logging_callback<args_t...> = func;
}
.cpp
template <typename ...args_t>
logging_callback_t<args_t...> logging_callback;
std::string to_append("something");
log(severity_t::warn, "abc {}", to_append);
客户端应用程序:
template <typename ...args_t)
void log_from_lib_a(severity_t level, const std::string& msg, args_t&& args...)
{
if(level == severity_t::warn)
{
spdlog::warn(msg, std::forward<args_t>(args)...);
}
}
int main()
{
set_logging_cbk<???>(boost::bind(&log_from_lib_a<???&...>,
boost::placeholders::_1,
boost::placeholders::_2,
???);
}
关于可变模板参数的实际类型,我在代码中唯一知道的地方是从库中进行日志记录。客户端如何注册一个他不知道参数的回调?这是我第一次使用参数包,所以我希望代码/想法至少在某种程度上是合理的。除此之外,为什么在std::function:的实例上调用方法时需要指定可变参数类型
logging_callback<args_t...>(level, msg, std::forward<args_t>(args)...);
而不是
logging_callback(level, msg, std::forward<args_t>(args)...);
PS:cstd++11是必需的,但如果有更好的解决方案和更新的版本,我也很高兴听到它!
您不能有一个虚拟模板方法,这主要是您想要的std::function
后面的类型擦除。
当你控制你的库时,你知道每个原型都是真正使用的,所以你可以创建一个符合你需求的记录器接口:
struct ILogger
{
virtual ~ILogger() = default;
virtual void write(severity_t, const std::string&) = 0;
virtual void write(severity_t, const std::string&, int) = 0;
virtual void write(severity_t, const std::string&, int, const std::string&) = 0;
virtual void write(severity_t, const std::string&, float) = 0;
// ...
};
void set_logger(ILogger&); // custom point
然后提供一种从变函数轻松创建类的方法,比如:
template <typename Functor>
struct LoggerT : ILogger, Functor
{
using Functor::Functor;
using Functor::operator();
void write(severity_t level, const std::string& format) override { (*this)(level, format);}
void write(severity_t level, const std::string& format, int v1) override { (*this)(level, format, v1);}
void write(severity_t level, const std::string& format, int v1, const std::string& v2) override { (*this)(level, format, v1, v2);}
void write(severity_t level, const std::string& format, float v1) override { (*this)(level, format, v1);}
// ...
};
template <typename Func>
LoggerT<Func> make_logger(Func func) { return {func}; }
然后你的客户只需要写:
struct SpdLogger {
template <typename ... Ts>
void operator () (severity_t level, const std::string& msg, Ts&& args...)
{
if (level == severity_t::warn)
{
spdlog::warn(msg, std::forward<Ts>(args)...);
}
}
};
int main()
{
LoggerT<SpdLogger> logger;
set_logger(logger);
// ...
}
在C++14中,使用通用lambda:
auto logger = make_logger([](severity_t level, const std::string& msg, auto&& args...)
{
if(level == severity_t::warn)
{
spdlog::warn(msg, decltype(args)(args)...); // decltype(args) is the forward
}
});
set_logger(logger);