可能重复:
纯虚函数和抽象类
我有一个类,我想创建一个用于打印调试信息的虚函数。任何继承这个类的类都必须实现这个虚函数。这样,不同的类可以将调试消息打印到不同的输出设备,例如std::cout
标签、文件等。
基类不会知道消息的目的地。但我不知道最好的方法来做到这一点。我正在考虑使用printf()
可以打印任意数量参数的东西。但我不知道那会实现什么。有任何想法吗?
您提出的方法有一个缺点。每个派生类都必须自己实现类似 printf 的设施。这似乎不是一项非常有成效的活动,并且会使基类使用起来非常烦人。
在代码方面,让基类要求派生类提供 debug 可能不那么繁琐ostream
。或者,您可以默认为cerr
.
class Base {
protected:
virtual std::ostream & debug_stream () const { return std::cerr; }
//...
};
然后,外部用户Base
可以将信息插入debug_stream
.
如果您坚持使用 printf 样式语法,则接口可能会返回 a 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;
//...
}
我会这样做
class Base
{
virtual int debugfn(const std::string& msg) const = 0;
};
即,不要将printf
(生成格式化字符串并将其发送到标准输出)的功能与调试功能混为一谈。让它把一个已经完成的字符串作为它的参数,并让派生类决定如何处理它。如果需要,int
可以是错误代码,否则只需 return void
。
我会做这样的事情:
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);
}
这样,您仍然可以为调用者提供灵活的格式,但派生类不必担心这一点,因为它们只给出了预先格式化的文本。
这里的基本思想是使用一个受保护的虚函数,它将一个字符串作为要打印/记录的错误/日志/调试消息。然后,您的基类公开一个非虚函数(或一组函数)来构造将馈送到受保护虚函数的字符串。要实际创建字符串,您可以使用多种方法中的一种,以 printf 的样式(使用可变参数或可变参数模板 (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;
};
上述设置的一个优点是,除了错误消息之外,您几乎可以插入任何您想要的内容,例如时间戳,或者只是添加“错误:”字符串或其他任何内容,这样您就不必在您发出消息的地方(呼叫站点)一直重复这一点。
我可能没有理解这个问题,因为在所有其他答案中都没有提供想到的最简单的东西......如果意图是为所有层次结构提供一个单一的入口点来打印信息,那么这是最简单的方法:
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
以控制是否仅打印对象的公共状态或还有辅助数据...)以产生更丰富的输出。
通过单个实现(和转发功能),您可以将对象打印到流中,并以一种方式生成带有程序状态的日志以进行调试。
另一方面,如果您的意思是派生类在不同时间点打印有关其状态的调试消息的解决方案,那么您不能真正多态地做到这一点,因为派生类型决定记录消息以及何时采取. 在这种情况下,只需拉出一个日志库并使用它。如果日志级别低于消息的类型(即,如果您配置为仅记录警告,则调试日志(不会生成)的成本非常小),则通用日志库在运行时的成本非常低。