我有一个日志记录宏,我想扩展它以包含调用它的对象实例的名称。
我为所有要继承的对象引入了一个基类,例如:
class ObjectInstance
{
public:
ObjectInstance( string name );
virtual string GetName() const = 0;
}
宏看起来像这样:
#define LOG( type, msg ) LogHandler( type, msg, dynamic_cast<ObjectInstance*>( this ) )
使用 LogHandler 功能,如:
void LogHandler( string type,
string msg,
ObjectInstance* instance )
{
string instanceName = "Unknown";
if( instance != NULL )
{
instanceName = instance->GetName();
}
const string output = type + " | " + msg + " | " + instanceName;
// Do logging stuff...
}
这似乎正在淘汰,但我很快意识到,当从静态上下文中调用宏时,它将无法编译...我正在绘制一个关于如何继续并且没有在网上显示太多的空白远。
非静态上下文示例:
class SomeObject : public ObjectInstance
{
SomeObject( const string& name )
:
m_name( name )
{
LOG( Info, "Created SomeObject" )
}
virtual string GetName() const { return m_name; }
}
SomeObject* ob = new SomeObject( "SomeObject123" );
预期输出:信息|创建SomeObject | SomeObject123
静态上下文示例 - 当然不会编译:
class SomeStaticObject
{
static void DoSomething()
{
LOG( Info, "Doing something..." )
}
}
SomeStaticObject::DoSomething();
预期输出:信息|做某事...... |
我真的很想在静态和非静态上下文中只使用一个宏,因为它已经在大型代码库中广泛使用。
答案 0 :(得分:2)
我为所有要继承的对象引入了一个基类
哇!停在那儿。这是c ++,而不是脚本语言。
为项目中的每个类添加一个公共基类限制了灵活性,将所有内容与记录器紧密耦合,并将您从滑坡上轻推到ObjectInstance
的集合,这会破坏所有类型的安全。
有一种更好的方法:
首先,定义一个标记类:
template<class T>
struct name_tag
{};
然后使LogHandler成为模板函数:
template<class T>
void LogHandler( string type,
string msg,
T* instance )
{
// get the name by deferring to instance_name(name_tag<>);
.... << instance_name(name_tag<std::decay_t<T>>()) << ...
}
现在我们只需要为传递给LogHandler的任何类型编写instance_name。
// this is your object
struct MyThing {};
// this is its name_generator.
const char* instance_name(name_tag<MyThing>)
{
return "MyThing";
}
请注意,每个名称生成器的返回类型可以是您想要的任何内容,只要它是可流式的。
完整示例,包括可选内容 - printer:
#include <string>
#include <iostream>
#include <iomanip>
#include <sstream>
#define LOG(type, msg, obj) LogHandler(type, msg, std::addressof(obj))
template<class T>
struct name_tag
{};
template<class T> const char* instance_name(name_tag<T>) { return typeid(T).name(); }
template<class T> const char* object_contents(const T*) { return "{}"; }
template<class T>
void LogHandler( std::string type,
std::string msg,
T* instance )
{
std::clog << "type: " << type
<< ", message: " << std::quoted(msg)
<< " : object type: " << instance_name(name_tag<std::decay_t<T>>())
<< " " << object_contents(instance)
<< std::endl;
}
// this is your object
struct MyThing {
int a = 4;
double b = 6.6;
};
// this is its name_generator.
const char* instance_name(name_tag<MyThing>)
{
return "MyThing";
}
// this is a contents printer.
std::string object_contents(const MyThing* p)
{
std::ostringstream ss;
ss << "{ a: " << p->a
<< ", b: " << p->b
<< " }";
return ss.str();
}
int main()
{
MyThing t;
LOG("whatever goes here", "hello", t);
}
预期产出:
type: whatever goes here, message: "hello" : object type: MyThing { a: 4, b: 6.6 }