C++-创建虚拟函数以打印调试消息的最佳方式

C++ - Best way to create a virtual function to print debug messages

本文关键字:消息 最佳 方式 调试 打印 创建 虚拟 函数 C++-      更新时间:2023-10-16

可能重复:
纯虚拟函数与抽象类

我有一个类,我想创建一个虚拟函数,用于打印调试信息。任何继承此类的类都必须实现此虚拟函数。通过这种方式,不同的类可以将调试消息打印到不同的输出设备,如std::cout、标签、文件等。

基类不会知道消息的目的地。但我不知道最好的方法。我正在考虑使用类似printf()的东西,它可以打印任意数量的参数。但我不知道会实现什么。有什么想法吗?

您提出的方法有一个缺点。每个派生类都必须自己实现类似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;
    //...
}

我会这样做:

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); 
}

这样,您仍然可以为调用者提供灵活的格式,但派生类不必担心这一点,因为它们只提供了预先格式化的文本。

我只想做这个

class Base
{
   virtual int debugfn(const std::string& msg) const = 0;
};

也就是说,不要将printf的功能(生成格式化字符串并将其发送到stdout)与调试功能混合使用。让它将一个已经完成的字符串作为参数,并让派生类决定如何处理它。如果需要,int可以是错误代码,否则只返回void

这里的基本思想是使用一个受保护的虚拟函数,该函数将字符串作为要打印/记录的错误/log/debug消息。然后,基类公开一个非虚拟函数(或一组函数)来构造将被提供给受保护的虚拟函数的字符串。要真正创建字符串,可以使用多种方式之一,即printf风格(使用varargs或varadic模板(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_lineint indent——如果!single_line插入前导空格,int verbosity控制是否只打印对象的公共状态或辅助数据…),以产生更丰富的输出。

通过一个实现(和一个转发函数),您可以将对象打印到流中,并可以使用程序状态生成日志以进行调试。

另一方面,如果您的意思是派生类在不同的时间点打印关于其状态的调试消息的解决方案,那么您就不能真正以多态的方式做到这一点,因为记录消息以及何时记录消息的决定应该由派生类型做出。在这种情况下,只需提取一个日志记录库并使用它。如果日志级别低于消息类型,则常见的日志记录库在运行时的成本非常低(即,如果您配置为只记录警告,则调试日志(不会生成)的成本非常小)。