我想实现一个流式日志库。
我创建了一个
Log_t
类来缓冲日志条目,并对其进行实际输出
被毁了,就像这样:
class Log_t: public std::ostringstream {
static int log_id;
public:
Log_t() { ++log_id; };
~Log_t() override {
// in order to simplify discussion, here output it to cout
std::cout << "log" << log_id << ':' << str() << std::endl;
};
};
int Log_t::log_id {};
template <typename T>
Log_t& operator<<( Log_t& log_, const T& body_ ) {
static_cast<std::ostream&>( log_ ) << body_;
return log_;
};
每当我有自定义的类/结构时,我都可以用相同的方式记录它,只要 我为它实现了一个输出函数。像这样:
struct Response_t {
int _id;
};
// Using std::ostream instead of Log_t, as it may be outputed to other targets
std::ostream& operator<<( std::ostream& os_, const Response_t& rsp_ ) {
return os_ << "resp_id:" << rsp_._id;
};
然后我可以将其记录为:
Response_t response {123};
Log_t() << "local obj=" << response;
但实际上Response_t主要是由lib返回,并且以指针形式返回 一个真正的ojb/ref,所以我可能应该输出所指向的内容 而不是地址,并且在记录之前我必须检查它是否为 nullptr。 此外,我不想检查每个自定义输出中是否为 nullptr 函数,相反,我只想在日志记录函数模板中检查一次。 我把函数模板改成了这两个:
template <typename T>
Log_t& operator<<( Log_t& log_, const T* body_ ) {
if( body_ == nullptr )
static_cast<std::ostream&>( log_ ) << "{nullptr}";
else
static_cast<std::ostream&>( log_ ) << *body_;
return log_;
};
template <typename T>
Log_t& operator<<( Log_t& log_, const T& body_ ) {
static_assert( ! std::is_null_pointer_v<T> );
static_assert( ! std::is_pointer_v<T> );
static_cast<std::ostream&>( log_ ) << body_;
return log_;
};
完整代码:
#include <iomanip>
#include <iostream>
#include <sstream>
#include <type_traits>
class Log_t: public std::ostringstream {
static int log_id;
public:
Log_t() { ++log_id; };
~Log_t() override { std::cout << "log" << log_id << ':' << str() << std::endl; };
};
int Log_t::log_id {};
/*
Let's say that parameter body_ is a pointer received from another lib,
so it may be a nullptr, and I don't wana check if it is a nullptr
everywhere in my projects, instead I want to check it only once in the
following function.
*/
template <typename T>
Log_t& operator<<( Log_t& log_, const T* body_ ) {
if( body_ == nullptr )
static_cast<std::ostream&>( log_ ) << "{nullptr}";
else
static_cast<std::ostream&>( log_ ) << *body_;
return log_;
};
template <typename T>
Log_t& operator<<( Log_t& log_, const T& body_ ) {
static_assert( ! std::is_null_pointer_v<T> );
static_assert( ! std::is_pointer_v<T> );
static_cast<std::ostream&>( log_ ) << body_;
return log_;
};
struct Response_t {
int _id;
};
std::ostream& operator<<( std::ostream& os_, const Response_t& rsp_ ) {
return os_ << "resp_id:" << rsp_._id;
};
int main() {
Response_t response {123};
Log_t() << "local obj=" << response;
// Suppose rsp_ptr is received from elsewhere, may be nullptr!
Response_t* rsp_ptr = &response;
Log_t() << "ptr obj=" << rsp_ptr;
rsp_ptr = nullptr;
Log_t() << "ptr obj=" << response;
exit( EXIT_SUCCESS );
};
但是我可以不能通过编译,并得到这个:
ostream-ptr.cpp:36:31:错误:静态断言失败 36 |
static_assert( ! std::is_pointer_v );
看起来指针的专门版本已经被gcc-12.3忽略了?
也许我可以用c++20的
concept
来解决它?
如何编码?
const T*
超载并没有被忽视。 const T&
重载更加专业化。
考虑这个例子:
template <typename T>
void f(const T*) {}
template <typename T>
void f(const T&) {}
int main() {
int i;
f(i); // calls the ref overload as expected, with T = int
int* p = &i;
f(p); // still calls the ref overload, but with T = int*
const int* pc = &i;
f(pc); // correctly calls the pointer overload, with T = int
}
如果添加第三个重载
void f(T*)
而没有 const
,则第二个调用 (f(p)
) 将使用该重载。
我假设您希望每当传递指针时都调用
const T*
重载,无论是否const
。在这种情况下,在 C++20 中,您可以添加 requires
子句来禁用 const T&
重载:
template <typename T>
requires(!std::is_pointer_v<T>)
Log_t& operator<<( Log_t& log_, const T& body_ ) { ... }
C++20之前,您可以类似地使用SFINAE。