Inheriting and overriding ostream operator in C++
Asked Answered
L

6

10

I've been trying to find an answer to this, but no one seems to have exactly the same problem as I do.

I am working with several derived classes. The ostream operator << for each of these should print out some things common to each, and some things specific to each. Later on, I would like to further derive from these derived classes, and again the new derived classes need to print out some things that are in the "generations" above them.
For example:

The Base class .h file

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;

};

The Base.cpp file includes these lines:

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

Then I derive: (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;
}

Then I want to derive:

class SecondDerived: Public FirstDerived
{ 

int ThirdClassNumber;

};

Second.cpp:

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

 FirstDerived::operator<<

 << "The third number is "

 << ThirdClassNumber;

 }

I think the problem is most likely either the declaration in the very start of the program, or the lines like Base::operator<<.

Another possibility is that I'm not redeclaring it in the .h file of every inherited class. Should I be, and if so what syntax should I use?

It was suggested to me to use the static_cast method, but my professor (the one who wrote the assignment, and therefore won't give us too much help with it) said that there's a better way to do it. Any suggestions?

Lakendra answered 17/5, 2011 at 6:56 Comment(1)
"I think the problem is most likely..." - what symptoms are you observing? Compilation error? Line, message? Or undesired run-time behaviour? If so what, and what did you expect?Flail
P
16

A simple technique for this is:

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;
        }
};
Perdure answered 17/5, 2011 at 7:27 Comment(0)
I
7

You cannot implement operator<< for ostreams as a class member - it has to be a free (possibly friend) function. This is because in the expression:

os << x;

the thing on the left-hand side of the << will not be an instance of your class, which it would have to be if it were a member function.

To call the parent from the child - do a static_cast:

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

which I think is the "best" solution. Alternatively, give your classes a a named function call Print() which takes an ostream as a parameter and use this to implement your operator<<. This will result in much cleaer code.

Inexistent answered 17/5, 2011 at 7:3 Comment(4)
so I need to redefine it as 'code'friend ostream& operator << (ostream& os, const Base &base)'/code' in the Base.h amd then again as 'code'friend ostream& operator << (ostream& os, const FirstDerived &firstderived)'/code', etc?Lakendra
then how do I call the parent operator from the child?Lakendra
That is exactly what OP is trying to avoid. The static_cast.Aldridge
while ugly, in this case the virtual operator<< member function is there to be invoked by the non-member streaming operator (which is correctly specified).Flail
S
4

Aside from what @Neil says, it would probably be better to implement a virtual DoStream method, so you don't need the upcasting:

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

So you only need to implement the operator once. You could also make the operator<< non-friend and the DoStream public, but that is probably personal preference.

Sanction answered 17/5, 2011 at 7:20 Comment(5)
@Juraj: Well, you edited it in while I wrote the answer, so I wouldn't know.Sanction
The original code in the question already has a virtual function in the base class akin to doStream, strangely called operator<<.Flail
@Juraj, @Sanction - thank you! Please let me know if I've got this right - when I actually want to print out an instance of Derived, the line I want will be std::cout << derived; and that will call to the Base operator, which in turn calls to the function which is specified for each derived class, and within it uses the Base function.Lakendra
@Tony - what do you suggest? I didn't quite understand your comment to Neil. I'm really a beginner at this.Lakendra
@BIU: well, what Xeo and Martin suggests is basically the same as what you had - with the correction given in my answer - except they avoid using operator<< in favour of a nicely named virtual function in the base class. Overall, I think a proper identifier is less cryptic than a operator being used in an atypical way, so Xeo or Martin's answers are an improvement - no significant difference between them (hence a +1 for both from me).Flail
F
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;
}

You need to call the function by putting parenthesis after it, and provide the argument it expects. It doesn't have a return value so shouldn't be in the set of things being streamed. Summarily:

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
Flail answered 17/5, 2011 at 7:34 Comment(3)
Thank you! I'm going to play around with your suggestion and those of @Juraj, @ Xeo and the other commenters and see what works the best for my program. All the best:)Lakendra
Would it work with istream too?Tolliver
@APars I'm not sure what "it" is - presumably you're not stuck using the same code as the person who asked this question. The proper way to support istreams for a type you've created called X is to write a function std::istream& operator>>(std::istream& is, const X& x) { ... }; Often it's good enough if the body of the function is return os >> x.member1 >> x.member2 >> x.member3; (for however-many members). It gets more complex if e.g. you want strings to read across whitespace, or to handle units specifiers (like GB and MB for gigabyte and megabyte).Flail
A
0

To call a method from base class you can use:

Base::method(/*parameters*/)

But operator<< is a free function. The only possibility without static_cast I can see is to define the operator as a template and then explicitly call the specialization like this:

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

This could be done the same way for operator<<, just change the name function for operator<< and add required parameters.


Another possibility is to make a virtual member function:

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

In Derived class this could call the Base print function:

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

Then it is enough to have operator<< only for the Base class and it will call appropriate print method polymorphicaly. Note that the operator<< is a free function.

ostream &operator<< (ostream &os, Base const &b) {
    b.print(os);
    return os;
}
Aldridge answered 17/5, 2011 at 7:9 Comment(1)
Do I need to redeclare operator << in each derived class's header file as well, or only override it in the child?Lakendra
G
0

Here's a slight modification to Loki's answer. Instead of having a virtual serialize method, I use a virtual to_string method. I think this can be reused in more contexts outputing to an ostream, which might be of use.

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

Produces

Base: 1 
FirstDerived: 1 2
Goldston answered 25/3, 2013 at 14:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.