我正在按照printing pattern for a class hierarchy中所述实现C++ FAQ。在FAQ中,打印函数在基类中声明如下:

protected:
  virtual void printOn(std::ostream& o) const = 0;  // pure virtual
  // -- or --
  virtual void printOn(std::ostream& o) const;      // plain virtual

我正在考虑实现printOn方法的纯虚拟版本,但有所不同。我想将返回类型从void更改为std::ostream&,如下所示:

protected:
  virtual std::ostream& printOn(std::ostream& o) const;

如我所见,这种方法的优点是可以使用链接方法将基类的输出更容易地合并到派生类的输出中。这是一个例子:

std::ostream& DerivedClass::printOn(std::ostream& stream) const
{
  return stream
      << "<DerivedClass>" << '\n'
      << BaseClass::printOn(stream)
      << "<member_one>" << member_one_ << "</member_one>" << '\n'
      << "<member_two>" << member_two_ << "</member_two>" << '\n'
      << "</DerivedClass>" << std::endl;
}

相比之下,这是如果在FAQ中将虚拟DerivedClass::printOn方法声明为BaseClass::printOn的情况下void的外观:

void DerivedClass::printOn(std::ostream& stream) const
{
    stream << "<DerivedClass>" << '\n';
    BaseClass::printOn(stream);

    stream
        << "<member_one>" << member_one_ << "</member_one>" << '\n'
        << "<member_two>" << member_two_ << "</member_two>" << '\n'
        << "</DerivedClass>" << std::endl;
}

问题:有人对我建议的printOn返回类型的修订版有任何陷阱吗?

最佳答案

如果BaseClass::printOn(stream)返回std::ostream &,那么您将无法编写

stream << ... << BaseClass::printOn(stream) << ...;

您必须写:
stream << ...;
BaseClass::printOn(stream) << ...;

显然,这几乎比返回void的情况更好。您可以使用无操作流输出运算符返回类型:
struct noop_manipulator {
  noop_manipulator(std::ostream &) {}
  friend inline std::ostream &operator<<(std::ostream &os, const noop_manipulator &) {
    return os;
  }
};

noop_manipulator DerivedClass::printOn(std::ostream& stream) const
{
    return stream << ...;
}

另外,您也可以使用以下语法:
void DerivedClass::printOn(std::ostream& stream) const
{
    return stream << "<DerivedClass>" << '\n',
        BaseClass::printOn(stream), stream
        << "<member_one>" << member_one_ << "</member_one>" << '\n'
        << "<member_two>" << member_two_ << "</member_two>" << '\n'
        << "</DerivedClass>" << std::endl;
}

所有这些方法的缺点是它们使您无法使用父类(super class)虚拟调用,这是非常不寻常的,因此实际上应该使其尽可能清晰。所描述的打印模式本身非常不常见;除非您已经具有虚拟继承层次结构,否则派生类通常具有自己的operator<<并通过operator<<调用基类static_cast到基类引用。

09-08 09:30