如何在输出的共享库中隐藏宏的使用?
如何在输出的共享库中隐藏宏的使用?
嘿,看起来你是想把Boost.Log的实现彻底藏在共享库背后,不让用你库的人感知到Boost的存在对吧?这其实就是典型的封装需求,我来给你捋捋具体怎么搞:
首先,你得把所有Boost相关的代码从公共头文件里彻底清掉——现在你的头文件里全是BOOST_LOG的宏和include,用户用你的库还得连带装Boost、引Boost的头,这完全不符合封装的初衷。
第一步:重构公共头文件,只留用户需要的接口
公共头(logger.hpp)应该只暴露用户要用到的核心内容:比如你自己定义的日志级别枚举,还有日志的接口(不管是宏还是函数),剩下的全藏到Pimpl里。举个例子:
#include <memory> #include "config.hpp" namespace Logger { // 自己定义日志级别,完全和Boost脱钩 enum class SeverityLevel { Debug, Info, Warning, Error, Fatal }; // 用来支持流操作的包装类,用Pimpl藏实现细节 class LogStream { public: LogStream(SeverityLevel level); ~LogStream(); // 支持流插入操作,模板函数适配各种类型 template <typename T> LogStream& operator<<(const T& value); private: struct Impl; std::unique_ptr<Impl> mImpl; }; // 保留原来的初始化函数 void init(const Config& config); // 替换原来的LOG宏,现在调用的是我们自己的LogStream #define LOG(severity) Logger::LogStream(Logger::SeverityLevel::##severity) }
看到没?这里完全没有Boost的影子,用户只需要知道用LOG(Debug) << "测试消息"就行,根本不用管背后是Boost还是别的什么日志库。
第二步:把Boost的逻辑全移到库的实现文件里
所有Boost的include、宏、日志初始化逻辑,都放到你的共享库的cpp文件(比如logger.cpp)里,用内部实现来对接Boost的接口。比如:
#include "logger.hpp" // 这里才引入Boost的头文件,外面完全看不到 #include <boost/log/sources/severity_channel_logger.hpp> #include <boost/log/sinks.hpp> #include <boost/log/attributes.hpp> #include <boost/log/expressions.hpp> namespace { // 库内部用的Boost日志器,对外完全隐藏 boost::log::sources::severity_channel_logger<Logger::SeverityLevel> internal_logger; // 把我们自己的日志级别转成字符串,用于Boost的日志格式化 const char* severity_to_string(Logger::SeverityLevel level) { switch(level) { case Logger::SeverityLevel::Debug: return "DEBUG"; case Logger::SeverityLevel::Info: return "INFO"; case Logger::SeverityLevel::Warning: return "WARNING"; case Logger::SeverityLevel::Error: return "ERROR"; case Logger::SeverityLevel::Fatal: return "FATAL"; default: return "UNKNOWN"; } } } namespace Logger { // 实现LogStream的Impl结构体,藏Boost的流逻辑 struct LogStream::Impl { Impl(SeverityLevel level) { // 打开Boost的日志记录 auto rec = internal_logger.open_record(boost::log::keywords::severity = level); if (rec) { strm.assign_record(std::move(rec)); } } boost::log::record_ostream strm; }; // LogStream的构造析构 LogStream::LogStream(SeverityLevel level) : mImpl(std::make_unique<Impl>(level)) {} LogStream::~LogStream() = default; // 流插入操作的实现,直接转发给Boost的流 template <typename T> LogStream& LogStream::operator<<(const T& value) { if (mImpl->strm) { mImpl->strm << value; } return *this; } // 显式实例化常用的类型,避免链接问题(用户用这些类型不会报错) template LogStream& LogStream::operator<<(const char* const&); template LogStream& LogStream::operator<<(const std::string&); template LogStream& LogStream::operator<<(int const&); template LogStream& LogStream::operator<<(float const&); template LogStream& LogStream::operator<<(double const&); // 初始化函数,原来的Boost配置逻辑全放这 void init(const Config& config) { // 比如从config里读日志文件路径、输出级别这些 auto sink = boost::log::add_file_log(config.log_file_path); // 设置日志格式,用我们自己的级别转字符串函数 sink->set_formatter( boost::log::expressions::stream << "[" << boost::log::expressions::format_date_time<boost::posix_time::ptime>("TimeStamp", "%Y-%m-%d %H:%M:%S") << "] [" << boost::log::expressions::callback( [](const boost::log::value_ref<SeverityLevel, boost::log::tag::severity>& level) { return severity_to_string(level.get()); }) << "] " << boost::log::expressions::smessage ); // 其他初始化逻辑,比如设置过滤级别、旋转日志这些 boost::log::core::get()->set_filter( boost::log::expressions::attr<SeverityLevel>("Severity") >= config.min_log_level ); } }
第三步:编译共享库时隐藏内部符号
最后,编译你的共享库的时候,要告诉编译器把内部符号(包括Boost的)藏起来,别导出给用户。比如用GCC/Clang的话,加-fvisibility=hidden编译选项;MSVC的话,给内部函数加__declspec(hidden)。这样用户链接你的库时,根本看不到任何Boost的符号,完全不需要依赖Boost的库文件。
额外注意点
- 如果你原来用的是Boost的
severity_level,现在要换成自己的枚举,然后在库内部做转换就行,用户完全不用管。 - 流操作的效率和原来用Boost宏差不多,因为都是直接转发到Boost的日志流,只是多了一层轻量包装,几乎没开销。
- Pimpl模式要注意析构函数的实现,别让编译器在头文件里生成析构函数(因为Impl的类型在头文件里是不完整的),要么把析构函数的实现放到cpp文件,要么像上面那样用
= default在cpp里定义。
这样改完之后,你的共享库对外完全看不到Boost的任何痕迹,用户只需要include你的logger.hpp,链接你的库就行,爽得很!
备注:内容来源于stack exchange,提问作者cppbird
相关产品推荐
相关产品推荐

