Наследование и переопределение оператора ostream в C++

Я пытался найти ответ на этот вопрос, но, кажется, ни у кого нет точно такой же проблемы, как у меня.

Я работаю с несколькими производными классами. Оператор 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;

};

Первый производный.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<<.

Другая возможность заключается в том, что я не объявляю его повторно в файле .h каждого унаследованного класса. Должен ли я быть, и если да, то какой синтаксис я должен использовать?

Мне было предложено использовать метод static_cast, но мой профессор (тот, кто написал задание, и поэтому не будет нам слишком сильно помогать с ним) сказал, что есть лучший способ сделать это. Какие-либо предложения?


person BIU    schedule 17.05.2011    source источник
comment
Я думаю, что проблема скорее всего... - Какие симптомы вы наблюдаете? Ошибка компиляции? Линия, сообщение? Или нежелательное поведение во время выполнения? Если да, то что и чего вы ожидали?   -  person Tony Delroy    schedule 17.05.2011


Ответы (6)


Простая техника для этого:

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;
        }
};
person Martin York    schedule 17.05.2011

Вы не можете реализовать оператор‹‹ для ostreams как член класса — это должна быть свободная (возможно, дружественная) функция. Это потому, что в выражении:

os << x;

то, что слева от ‹‹, не будет экземпляром вашего класса, каким оно должно было бы быть, если бы оно было функцией-членом.

Чтобы вызвать родителя из дочернего элемента, выполните static_cast:

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

который я считаю "лучшим" решением. В качестве альтернативы дайте своим классам именованный вызов функции Print(), который принимает ostream в качестве параметра и использует его для реализации вашего оператора‹‹. Это приведет к гораздо более четкому коду.

person Community    schedule 17.05.2011
comment
поэтому мне нужно переопределить его как 'code'friend ostream& operator ‹‹ (ostream& os, const Base &base)'/code' в Base.h amd, а затем снова как 'code'friend ostream& operator ‹‹ (ostream& os, const FirstDerived &firstderived)'/code' и т. д.? - person BIU; 17.05.2011
comment
тогда как мне вызвать родительский оператор из дочернего? - person BIU; 17.05.2011
comment
Это именно то, чего ОП пытается избежать. static_cast. - person Juraj Blaho; 17.05.2011
comment
хотя это и уродливо, в этом случае виртуальная функция-член operator<< должна вызываться оператором потоковой передачи, не являющимся членом (который указан правильно). - person Tony Delroy; 17.05.2011

Помимо того, что говорит @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 общедоступным, но это, вероятно, личное предпочтение.

person Xeo    schedule 17.05.2011
comment
@Juraj: Ну, ты редактировал его, пока я писал ответ, так что я не знал. - person Xeo; 17.05.2011
comment
Исходный код в вопросе уже имеет виртуальную функцию в базовом классе, похожую на doStream, со странным названием operator<<. - person Tony Delroy; 17.05.2011
comment
@Juraj, @Xeo - спасибо! Пожалуйста, дайте мне знать, правильно ли я понял - когда я действительно хочу распечатать экземпляр Derived, строка, которую я хочу, будет std::cout ‹‹ производной; и это будет вызывать базовый оператор, который, в свою очередь, вызывает функцию, указанную для каждого производного класса, и внутри него использует базовую функцию. - person BIU; 17.05.2011
comment
@ Тони, что ты предлагаешь? Я не совсем понял твой комментарий Нилу. Я действительно новичок в этом. - person BIU; 17.05.2011
comment
@BIU: ну, то, что предлагают Ксео и Мартин, в основном то же самое, что и у вас - с исправлением, указанным в моем ответе, - за исключением того, что они избегают использования operator<< в пользу красиво названной виртуальной функции в базовом классе. В целом, я думаю, что правильный идентификатор менее загадочен, чем оператор, используемый нетипичным образом, поэтому ответы Xeo или Martin являются улучшением - между ними нет существенной разницы (следовательно, +1 для обоих от меня). - person Tony Delroy; 17.05.2011

Первый производный.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
person Tony Delroy    schedule 17.05.2011
comment
Благодарю вас! Я собираюсь поиграть с вашим предложением, предложениями @Juraj, @Xeo и других комментаторов и посмотреть, что лучше всего подходит для моей программы. Всего наилучшего:) - person BIU; 17.05.2011

Чтобы вызвать метод из базового класса, вы можете использовать:

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

Тогда достаточно иметь operator<< только для класса Base и он будет полиморфно вызывать соответствующий метод печати. Обратите внимание, что operator<< — бесплатная функция.

ostream &operator<< (ostream &os, Base const &b) {
    b.print(os);
    return os;
}
person Juraj Blaho    schedule 17.05.2011
comment
Нужно ли мне повторно объявлять оператор ‹‹ также в заголовочном файле каждого производного класса или переопределять его только в дочернем? - person BIU; 17.05.2011

Вот небольшая модификация ответа Локи. Вместо виртуального метода сериализации я использую виртуальный метод 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
person Chad Skeeters    schedule 25.03.2013