【问题标题】:Inheriting and overriding ostream operator in C++在 C++ 中继承和覆盖 ostream 运算符
【发布时间】:2011-08-27 00:08:29
【问题描述】:

我一直在努力寻找答案,但似乎没有人遇到与我完全相同的问题。

我正在处理几个派生类。 ostream 操作符 例如:

基类 .h 文件

class Base

{  



 int FirstClassNumber;

//The declaration I'm currently working with, that a friend gave me
//I'm pretty sure my problem lies here.


public:

friend ostream& operator << (ostream& os, const Base &base)
{
    base << os ;

    return os;
}

virtual void operator << (ostream& os) const = 0;

};

Base.cpp 文件包括以下几行:

void Base::operator << (ostream& os)
{
  os << FirstClassNumber;
}

然后我推导:(FirstDerived.h)

class FirstDerived : Public Base

{ 

int SecondClassNumber;

};

FirstDerived.cpp:

FirstDerived::operator << (ostream& os)
{
  os <<

  "The first Number is:

 //This is the line that isn't working - someone else gave me this syntax

  << Base::operator<< 

  << "The second number is"

  << SecondClassNumber;
}

那我要推导:

class SecondDerived: Public FirstDerived
{ 

int ThirdClassNumber;

};

第二个.cpp:

FirstDerived::operator << (ostream& os)
{
  os <<

 FirstDerived::operator<<

 << "The third number is "

 << ThirdClassNumber;

 }

我认为问题很可能是程序一开始的声明,或者像Base::operator&lt;&lt; 这样的行。

另一种可能性是我没有在每个继承类的 .h 文件中重新声明它。我应该是,如果是,我应该使用什么语法?

有人建议我使用static_cast 方法,但我的教授(写作业的人,因此不会给我们太多帮助)说有更好的方法来做。有什么建议吗?

【问题讨论】:

  • “我认为问题很可能是......” - 您观察到什么症状?编译错误?线路,消息?还是不希望的运行时行为?如果是这样,你期望什么?

标签: c++ inheritance operator-overloading ostream


【解决方案1】:

这里对 Loki 的回答稍作修改。我没有使用虚拟序列化方法,而是使用虚拟 to_string 方法。我认为这可以在更多输出到 ostream 的上下文中重用,这可能有用。

#include <iostream>
#include <string>

class Base
{
public:
  Base();
  virtual std::string to_string() const;

protected:
  int first_class_number;
  friend std::ostream& operator<<(std::ostream& os, const Base &base);
};

Base::Base()
  : first_class_number(1)
{
}

std::string Base::to_string() const
{
  return "Base: "+std::to_string(first_class_number);
}



class FirstDerived : public Base
{
public:
  FirstDerived();
  std::string to_string() const;

protected:
  int second_class_number;
};

FirstDerived::FirstDerived()
  : second_class_number(2)
{
}

std::string FirstDerived::to_string() const
{
  return "FirstDerived: "+std::to_string(first_class_number)+" "+ std::to_string(second_class_number);
}


std::ostream& operator << (std::ostream& os, const Base &base)
{
  os << base.to_string();
  return os;
}


int main(int argc, const char *argv[])
{
  std::cout << Base() << std::endl;
  std::cout << FirstDerived() << std::endl;
  return 0;
}

生产

Base: 1 
FirstDerived: 1 2

【讨论】:

    【解决方案2】:

    FirstDerived.cpp:

    FirstDerived::operator << (ostream& os)
    {
         os <<    "The first Number is:"
       //This is the line that isn't working - someone else gave me this syntax
        << Base::operator<<
         << "The second number is"
        << SecondClassNumber;
    }
    

    您需要通过在函数后面加上括号来调用函数,并提供它期望的参数。它没有返回值,因此不应该在流式传输的集合中。总结:

    os << "The first number is: "; // finish streaming statement with ";"
    Base::operator<<(os);   // separate statement to call this function...
    os << "The second number is " << SecondClassNumber; // start streaming again
    

    【讨论】:

    • 谢谢!我将尝试你的建议以及@Juraj、@Xeo 和其他评论者的建议,看看什么对我的程序最有效。一切顺利:)
    【解决方案3】:

    一个简单的技术是:

    class Base
    {  
        int FirstClassNumber;
    
        public:
            virtual void serialize(ostream& os) const
            {
                 os << FirstClassNumber;
            }
    };
    
    // Implement the stream operator for the base class.
    // All it does is call erialize which is a virtual method that
    // will call the most derived version.
    ostream& operator << (ostream& os, const Base &base)
    {
        base.serialize(os);
    
        return os;
    }
    
    class FirstDerived:public Base
    {  
        int SecondClassNumber;
    
        public:
            // Override serialize to make it call the base version.
            // Then output any local data.
            virtual void serialize(ostream& os) const
            {
                 Base::serialize(os);
                 os << SecondClassNumber;
            }
    };
    

    【讨论】:

      【解决方案4】:

      您不能将 ostreams 的 operator

      os << x;
      

      从孩子调用父母 - 做一个 static_cast:

      ostream & operator << ( ostream & os, const Child & c ) {
            os << static_cast <const Parent &>( c );
            // child stuff here
      }
      

      我认为这是“最佳”解决方案。或者,给你的类一个命名函数调用 Print(),它接受一个 ostream 作为参数,并使用它来实现你的 operator

      【讨论】:

      • 所以我需要在 Base.h amd 中将其重新定义为 'code'friend ostream& operator
      • 那我怎么从孩子那里调用父操作符呢?
      • 这正是 OP 试图避免的。 static_cast.
      • 虽然丑陋,但在这种情况下,虚拟operator&lt;&lt; 成员函数将由非成员流操作符(已正确指定)调用。
      【解决方案5】:

      要从基类调用方法,您可以使用:

      Base::method(/*parameters*/)
      

      operator&lt;&lt; 是免费功能。我可以看到没有 static_cast 的唯一可能性是将运算符定义为模板,然后像这样显式调用专业化:

      template<typename T>
      void function(T const &t);
      
      template<>
      void function<Base>(Base const &t) {
          // own implementation ...
      }
      
      template<>
      void function<Derived>(Derived const &t) {
          function<Base>(t);
          // own implementation ...
      }
      

      操作符function更改为operator&lt;&lt;并添加所需参数。


      另一种可能是做一个虚成员函数:

      class Base {
          virtual void print(ostream &os) const {
              // print self
          }
      }; 
      

      在派生类中,这可以调用基本打印函数:

      class Derived {
          virtual void print(ostream &os) const {
              Base::print(os);
              // print self
          }
      };
      

      那么 operator&lt;&lt; 仅用于 Base 类就足够了,它将多态地调用适当的打印方法。请注意,operator&lt;&lt; 是免费函数。

      ostream &operator<< (ostream &os, Base const &b) {
          b.print(os);
          return os;
      }
      

      【讨论】:

      • 我需要在每个派生类的头文件中重新声明运算符
      【解决方案6】:

      除了@Neil 所说的,实现一个虚拟的DoStream 方法可能会更好,所以你不需要向上转换:

      class Base{
      private:
        virtual void DoStream(ostream& os){
          // general stuff
        }
      public:
        friend ostream& operator<<(ostream& os, Base& b){
          b.DoStream(os);
          return os;
        }
      };
      
      class Derived : public Base{
      private:
        void DoStream(ostream& os){
          Base::DoStream(os);
          // derived specific stuff
        }
      };
      

      所以你只需要实现一次操作符。您也可以将operator&lt;&lt; 设为非好友,将DoStream 设为公开,但这可能是个人喜好。

      【讨论】:

      • @Juraj:嗯,你在我写答案的时候编辑了它,所以我不知道。
      • 问题中的原始代码在类似于doStream的基类中已经有一个虚函数,奇怪地称为operator&lt;&lt;
      • @Juraj,@Xeo - 谢谢!如果我做对了,请告诉我 - 当我真的想打印出 Derived 的实例时,我想要的行将是 std::cout
      • @Tony - 你有什么建议?我不太明白你对尼尔的评论。我真的是这方面的初学者。
      • @BIU:好吧,Xeo 和 Martin 的建议与您的建议基本相同——在我的回答中给出了更正——除了他们避免使用 operator&lt;&lt; 来支持一个命名良好的虚函数在基类中。总体而言,我认为正确的标识符比以非典型方式使用的运算符更隐蔽,因此 Xeo 或 Martin 的答案是一种改进 - 它们之间没有显着差异(因此我对两者都 +1)。