在C++中继承和重载ostream运算符

10

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

我正在使用几个派生类。每个派生类的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;

};

Second.cpp:

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

 FirstDerived::operator<<

 << "The third number is "

 << ThirdClassNumber;

 }

我认为问题很可能出现在程序开头的声明,或者像 Base::operator<< 这样的行中。
另一种可能是我没有在每个继承类的 .h 文件中重新声明它。 如果需要重新声明,那么应该使用什么语法?
有人建议我使用 static_cast 方法,但是我的教授 (写了这项任务并且不会给我们太多帮助) 说有更好的方法。 有什么建议吗?

我认为问题最有可能是...你观察到了什么症状?编译错误?行号,消息?还是不希望的运行时行为?如果是这样,你期望的是什么? - Tony Delroy
6个回答

16

一个简单的技巧是:

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

7
你不能将 ostream 的 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中将其重新定义为'code'friend ostream& operator << (ostream& os, const Base &base)'/code',然后再次定义为'code'friend ostream& operator << (ostream& os, const FirstDerived &firstderived)'/code'等等? - BIU
那么我如何从子操作符调用父操作符? - BIU
这正是 OP 试图避免的。static_cast - Juraj Blaho
虽然不太美观,但在这种情况下,虚拟的 operator<< 成员函数是由正确指定的非成员流操作符调用的。 - Tony Delroy

4

除了@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<<非友元并将DoStream公开,但这可能是个人偏好。


@Juraj:好的,你在我回答的时候编辑了它,所以我不知道。 - Xeo
问题中的原始代码已经在基类中有一个类似于doStream的虚函数,奇怪地被称为operator<< - Tony Delroy
@Juraj,@Xeo - 谢谢!请告诉我是否正确 - 当我想要打印Derived的实例时,我需要的行是std::cout << derived;这将调用Base运算符,进而调用为每个派生类指定的函数,并在其中使用Base函数。 - BIU
@Tony - 你有什么建议吗?我没有完全理解你对Neil的评论。我真的是个初学者。 - BIU
@BIU: 嗯,Xeo和Martin建议的基本上与你所拥有的相同 - 除了我在答案中提供的更正之外 - 只是他们避免使用operator<<,而是倾向于在基类中使用一个雅致命名的虚函数。总体而言,我认为适当的标识符比以非典型方式使用运算符要更清晰,因此Xeo或Martin的答案是一种改进 - 两者之间没有明显的区别(因此我会给他们双方+1)。 - Tony Delroy

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以及其他评论者的建议,看看哪个对我的程序最有效。祝一切顺利 :) - BIU
它也能与 istream 一起使用吗? - A P
1
@APars 我不确定 "it" 是什么 - 你可能不必使用与提问者相同的代码。支持你创建的类型 X 的 istreams 的正确方法是编写一个函数 std::istream& operator>>(std::istream& is, const X& x) { ... };。通常,如果函数体是 return os >> x.member1 >> x.member2 >> x.member3;(对于任意数量的成员),那么它就足够好了。如果您想要字符串跨空格读取,或处理单位说明符(如 GB 和 MB 表示千兆字节和兆字节),则会变得更加复杂。 - Tony Delroy

0

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

Base::method(/*parameters*/)

但是operator<<是一个自由函数。我所能看到的没有使用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<<并添加所需参数即可。


另一种可能性是创建一个虚成员函数:

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

在派生类中,这可以调用基类的打印函数:
class Derived {
    virtual void print(ostream &os) const {
        Base::print(os);
        // print self
    }
};

然后只需要在Base类中拥有operator<<,它将以多态的方式调用适当的打印方法。请注意,operator<<是一个自由函数。

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

我需要在每个派生类的头文件中重新声明运算符<<吗?还是只需要在子类中覆盖它就可以了? - BIU

0
这是对Loki答案的轻微修改。我使用了一个虚拟的to_string方法,而不是一个虚拟的serialize方法。我认为这可以在更多上下文中重复使用,比如输出到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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接