我有一个类,我想创建一个用于打印调试信息的虚函数。任何继承此类的类都必须实现此虚函数。这样,不同的类可以将调试消息打印到不同的输出设备,例如std::cout
,标签,文件等。
基类不会知道消息目标。但我不知道最好的方法。我正在考虑使用类似printf()
的东西来打印任意数量的参数。但我不知道会有什么成就。有什么想法吗?
答案 0 :(得分:2)
您建议的方法存在缺点。每个派生类都必须自己实现类似printf的设施。这似乎不是一个非常有效的活动,它会使基类使用起来非常烦人。
让基类要求派生类提供调试ostream
,代码可能不那么繁琐。或者,您可以默认为cerr
。
class Base {
protected:
virtual std::ostream & debug_stream () const { return std::cerr; }
//...
};
然后,Base
的外部用户可以将信息戳入debug_stream
。
如果您坚持使用printf样式语法,界面可能会返回FILE *
而不是
class Base {
protected:
virtual FILE * debug_file () const { return stderr; }
//...
};
因此,例如,派生类可以执行此操作:
class Derived : public Base {
static std::ofstream dbgstream;
std::ostream & debug_stream () const { return dbgstream; }
//...
};
std::ofstream Derived::dbgstream("/tmp/derived.dbg");
然后,如果有权访问基类接口,
void foo (Base *b) {
//...do something with b
b->debug_stream()
<< __FILE__ << ":" << __LINE__ << " foo did something"
<< std::endl;
//...
}
答案 1 :(得分:0)
我会这样做
class Base
{
virtual int debugfn(const std::string& msg) const = 0;
};
即,不要将printf
(生成格式化字符串并将其发送到stdout)的功能与调试功能混合使用。让它的参数作为一个已经完成的字符串,让派生类决定如何处理它。如果需要,int
可以是错误代码,否则只返回void
。
答案 2 :(得分:0)
我会做这样的事情:
class Base
{
protected:
virtual int doDebug(const std::string& Msg) const = 0;
public:
int Debug(const char* MsgFmt, ...) const;
};
int Base::Debug(const char* MsgFmt, ...) const
{
std::string sMsg;
va_list args;
va_start(args, MsgFmt);
int len = vsnprintf(NULL, 0, MsgFmt, args);
if (len > 0)
{
sMsg.resize(len);
vsnprintf(&sMsg[0], len + 1, MsgFmt, args);
}
va_end(args);
return doDebug(sMsg);
}
这样,您仍然可以为调用者提供灵活的格式,但派生类不必担心,因为它们只提供预格式化的文本。
答案 3 :(得分:0)
这里的基本思想是使用受保护的虚函数,该函数将字符串作为要打印/记录的错误/日志/调试消息。然后,您的基类公开一个非虚函数(或一组函数)来构造将被提供给受保护虚函数的字符串。要实际创建字符串,您可以使用printf(使用varargs或可变参数模板(C ++ 11))或标准iostream样式的多种方式之一。以下是后一类别中的一种解决方案:
class BaseDebugOutput {
protected:
virtual void printMessage(const std::string& aMsg) = 0;
private:
std::stringstream temp_stream;
public:
virtual ~BaseDebugOutput() { };
// One << operator for any type.
template <typename T>
BaseDebugOutput& operator<<(const T& value) {
temp_stream << value;
return *this;
};
typedef std::basic_ostream< std::stringstream::char_type,
std::stringstream::traits_type >&
(*ostream_function_ptr)(
std::basic_ostream< std::stringstream::char_type,
std::stringstream::traits_type >&);
// One << operator for things like std::endl.
BaseDebugOutput& operator<<(ostream_function_ptr p) {
if(p == ostream_function_ptr(std::endl)) {
// if the user outputted an end-line, then print the entry:
temp_stream << std::endl;
std::string temp_msg;
std::getline(temp_stream,temp_msg);
// call the protected virtual function:
printMessage(temp_msg);
} else {
temp_stream << p;
};
return *this;
};
};
示例派生类将是:
class CErrDebugOutput : public BaseDebugOutput {
protected:
virtual void printMessage(const std::string& aMsg) {
std::cerr << "Error reported with message: '" << aMsg << "'." << std::endl;
};
};
用例看起来像这样:
int main() {
BaseDebugOutput* debug_output = new CErrDebugOutput;
(*debug_output) << "The answer is: " << 42 << "!" << std::endl;
delete debug_output;
return;
};
上述设置的一个优点是,除了错误消息之外,您几乎可以插入任何内容,例如时间戳,或者只是添加“错误:”字符串或其他任何内容,以便您不要必须在发出消息的地方(呼叫站点)一直重复这一点。
答案 4 :(得分:0)
我可能没有理解这个问题,因为想到的最简单的事情并没有在所有其他答案中提供......如果意图是为所有层次结构提供单个入口点来打印信息,那么这个是最简单的方法:
class base {
public:
virtual std::ostream& print( std::ostream& /*...*/ ) const = 0;
};
std::ostream& operator<<( std::ostream& o, base const & b ) {
return b.print( o );
}
评论/*...*/
就在那里,因为与operator<<
不同,签名不是固定的,因此您可以传递额外的参数来控制格式(例如bool single_line
,int indent
--if !single_line
插入前导空格,int verbosity
控制是仅打印对象的公共状态还是辅助数据...)以产生更丰富的输出。
使用单个实现(和转发功能),您可以将对象打印到流中,并使用程序状态生成日志以进行调试。
另一方面,如果你的意思是派生类在不同的时间点打印关于它们状态的调试消息的解决方案,那么你不能真正做多态,因为决定记录消息和什么时候应该由派生类型。在这种情况下,只需拉一个日志库并使用它。如果日志级别低于消息类型,则通用日志记录库在运行时的成本非常低(即,如果配置为仅记录警告,则调试日志的成本(将不会生成)非常小。