【问题标题】:Inheriting from a C++ class with no virtual functions从没有虚函数的 C++ 类继承
【发布时间】:2013-01-27 15:15:03
【问题描述】:

我们有一个基于带有自定义流缓冲区的 std::ostream 的日志实现。我们通过 Schwarz 计数器实现我们应用程序的日志类实例。

为了避免将我们的低级类耦合到我们的日志实现,我们可以传递对 std::ostream 的引用。通过这种方式,我们的低级类可以记录到 std::cout、std::cerr 或通过 Schwarz 计数器创建的实例。

我有一个问题。日志实现通过流运算符的重载设置其严重性:

// Overload the << operator to set the log message severity
inline CLogStream& operator << (CLogStream& myLogStream, eMsgType::type msgTypeCurrent)
{
  myLogStream.SetMsgTypeCurrent(msgTypeCurrent);
  return ( myLogStream ) ;
} 

这允许我们像这样使用记录器:

CLog::Main << CLog::MSG_FATAL << "Fatal error" << std::endl;

我想创建一个对我们应用程序的日志实例的引用,该日志被锁定到特定的严重性。这样,我可以将两个 std::ostream 引用传递给我们的实用程序类。其中一个用于正常报告,另一个用于错误报告。这些可以设置为 std::cout 和 std::cerr,或者设置为引用我们的日志对象实例的某种对象。

不幸的是,据我所知,std::ostream 运算符

有什么想法吗?

【问题讨论】:

  • 如果SetMsgTypeCurrent 不是virtual(即CLogStream 不是为继承而设计的),那就很难了。
  • @Jon:我可以更改 SetMsgTypeCurrent 的定义,如果有帮助的话可以将其设为虚拟。但是我需要通过 std::ostream 访问该类,所以我看不到会给我什么选项。我知道的唯一虚函数在 std::streambuf 中,它们似乎都没有用于此目的。
  • 不是答案,但是:我也可以看到这种设计的其他问题。特别是,它不是线程安全的:另一个线程可能会干扰&lt;&lt; CLog::MSG_FATAL&lt;&lt; "Fatal error"。完全改变设计是一种选择吗?
  • @jogojapan:在设计类时,线程被排除在应用程序设计之外。有人建议我们放宽这一限制,因此几乎可以肯定设计更改正在酝酿之中。这种设计的另一个问题是,即使日志级别没有得到输出,也会生成日志消息。我认为我们可以通过某种包装器一起解决这两个问题,但这需要几个月的时间和设计审查。

标签: c++ dependencies


【解决方案1】:

iostream 具有虚拟成员函数(特别是~ios_base),因此您可以在operator&lt;&lt; 中执行dynamic_cast

inline std::ostream &operator<<(std::ostream &os, eMsgType::type msgTypeCurrent) {
  if (CLogStream *myLogStream = dynamic_cast<CLogStream *>(&os)) {
    myLogStream->SetMsgTypeCurrent(msgTypeCurrent);
  } else {
    os << "TYPE: " << static_cast<typename std::underlying_type<eMsgType::type>
      ::type>(msgTypeCurrent) << ": ";
  }
  return os;
}

【讨论】:

  • @PeterWood dynamic_cast 会有一些开销,但对于日志记录,它可能由 IO 主导。检测代码将揭示是否是这种情况。
  • 这个操作符不是必须暴露给我们的低级类以及日志类的至少某些部分吗?我想从低级类中删除日志类的所有知识。
  • @SimonElliott 然后不要内联它。只需在标头中声明它并在 lib 中实现它。我假设他们至少知道消息类型参数,否则这一切都毫无意义。 (和 +1,ecatmur,很好的答案)。
  • @SimonElliott 您可以使运算符非内联。或者,您可以将dynamic_cast 替换为dynamic_castCLogStream *,而不是eMsgType 旁边定义的接口。
【解决方案2】:

如果严重性的设置是持久的,那么这两行都会导致日志条目具有致命严重性

CLog::Main << CLog::MSG_FATAL << "Log entry 1: " << some_data << std::endl;
CLog::Main << "Log entry 2: " << some_other_data << std::endl;

那么您的日志记录类已经被正确设计为作为通用ostream&amp; 传递。对于实用程序类支持的不同日志级别,您只需要单独的记录器实例。

这是在假设记录器类已从ostream 继承以利用现有的operator&lt;&lt; 重载的情况下编写的。 在这种情况下,some_datasome_other_dataoperator&lt;&lt; 已经完全不知道输出进入了日志流。

【讨论】:

  • 这可行,但我希望在任何地方都使用相同的实例。用户可以向日志实例(文件、UDP、标准输出等)添加各种输出接口选项——除非我们添加某种“克隆”方法,否则这些不会出现在新实例中。
  • @SimonElliott:单个实例的问题是该实例无法确定下一个输出应该发生在哪个级别。另一方面,用户应该可以对不同的实例应用不同的选项。
  • 也许他们都可以共享流缓冲区:这是大部分实际工作发生的地方。
  • @SimonElliott:有可能,但不建议这样做。在多个流之间共享流缓冲区的后果是 1)缓冲区的生命周期管理变得越来越困难,2)如果数据同时写入多个流,输出将混合(导致不可读的日志),3)你强制这些流写入相同的目的地。
【解决方案3】:

了解ios_base::iword()。它使您可以访问流对象中的 long 值数组,您可以使用这些值来存储标志和特殊值等内容。

【讨论】:

    猜你喜欢
    • 2015-06-23
    • 1970-01-01
    • 2011-07-18
    • 2020-12-20
    • 2023-03-14
    • 1970-01-01
    • 2016-03-04
    • 2015-08-23
    相关资源
    最近更新 更多