Наследование и переопределение оператора 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;
};
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
метод, но мой профессор (тот, кто написал задание, и поэтому не будет помогать нам с этим) сказал, что есть лучший способ сделать это. Какие-либо предложения?
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;
}
};
Вы не можете реализовать оператор<< для ostreams как член класса - это должна быть бесплатная (возможно, друг) функция. Это потому, что в выражении:
os << x;
вещь слева от << не будет экземпляром вашего класса, каким он был бы, если бы он был функцией-членом.
Чтобы вызвать родителя от ребенка - сделайте static_cast:
ostream & operator << ( ostream & os, const Child & c ) {
os << static_cast <const Parent &>( c );
// child stuff here
}
который я считаю "лучшим" решением. В качестве альтернативы, дайте вашим классам именованный вызов функции Print(), который принимает ostream в качестве параметра, и используйте его для реализации вашего оператора<<. Это приведет к гораздо более четкому коду.
Помимо того, что говорит @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
публичный, но это, вероятно, личное предпочтение.
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
Для вызова метода из базового класса вы можете использовать:
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
}
};
В производном классе это может вызвать функцию печати Base:
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;
}
Вот небольшая модификация ответа Локи. Вместо виртуального метода сериализации я использую виртуальный метод 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