Справочная информация
Я программировал на Java некоторое время, и я переключился на C ++ всего несколько месяцев назад, поэтому я прошу прощения, если ответ — просто глупость, которую я пропустил! Теперь, когда это все сказано, настало время для проблемы! Я занимаюсь разработкой базового текстового игрового движка, и недавно я столкнулся с интересной специфической и маловероятной проблемой. Я попытался протестировать его в меньшем масштабе в программе ниже и решил просто показать это (в отличие от моего реального игрового кода), чтобы не задушить экран и сделать проблему менее запутанной. Проблема, смоделированная ниже, отражает проблему с моим настоящим кодом, просто без пушистых отвлекающих факторов.
Эта проблема
По сути, проблема заключается в полиморфизме. Хочу перегрузить оператор вывода<<«служить функцией отображения, уникальной для каждого объекта в иерархии. Проблема в том, что когда я вызываю этот оператор из списка, в котором хранятся эти элементы иерархии, они теряют свою идентичность и вызывают оператор вывода базового класса. Обычно Можно было бы решить эту проблему, заменив перегрузки оператора простым методом отображения, пометив метод отображения виртуальным и перейдя к их счастливому дню. Я не особо возражаю против внесения такого изменения в код, но теперь я просто Любопытно. Есть ли способ перегрузить операторы в иерархии, что приводит к тому, что я собираюсь здесь?
Код [Пример]
#include <vector>
#include <iostream>
#include <string>
using namespace std;
class Polygon {
friend ostream& operator<<(ostream& os, const Polygon& p);
public:
private:
};class Rectangle : public Polygon {
friend ostream& operator<<(ostream& os, const Rectangle& r);
public:
private:
};class Square : public Rectangle {
friend ostream& operator<<(ostream& os, const Square& s);
public:
private:
};
ostream& operator<<(ostream& os, const Polygon& p) {
os << "Polygon!" << endl;
return os;
}
ostream& operator<<(ostream& os, const Rectangle& r) {
os << "Rectangle!" << endl;
return os;
}
ostream& operator<<(ostream& os, const Square& s) {
os << "Square!" << endl;
return os;
}int main() {
vector<Polygon*> listOfPoly;
listOfPoly.push_back(new Polygon());
listOfPoly.push_back(new Rectangle());
listOfPoly.push_back(new Square());
for(Polygon* p : listOfPoly) {
cout << *p;
}
}
Выход для кода [Пример]
Polygon!
Polygon!
Polygon!
Требуемый вывод для кода [Пример]
Polygon!
Rectangle!
Square!
Есть ли способ перегрузить операторы в иерархии, что приводит к тому, что я собираюсь здесь?
Нет.
Проблема в том, что операторы не в ваша иерархия. friend
Ключевое слово здесь просто вперед-объявляет свободную функцию и дает ей привилегированный доступ к классу. Это не делает его методом, поэтому он не может быть виртуальным.
Обратите внимание, что перегрузка операторов — просто синтаксический сахар. Выражение
os << shape;
можно разрешить до свободной функции (как у вас здесь)
ostream& operator<< (ostream&, Polygon&);
или член левого операнда, такой как
ostream& ostream::operator<<(Polygon&);
(очевидно, здесь второй случай не существует, потому что вам придется изменить std::ostream
). Какой синтаксис не может решите, является членом правого операнда.
Таким образом, вы можете иметь свободный оператор функции (который обязательно является не виртуальным) или метод в левом операнде (который мог быть виртуальным), но не метод с правым операндом.
Обычное решение состоит в том, чтобы иметь единственную перегрузку для верхнего уровня иерархии, которая отправляется виртуальному методу. Так:
class Polygon {
public:
virtual ostream& format(ostream&);
};
ostream& operator<<(ostream& os, const Polygon& p) {
return p.format(os);
}
Теперь просто реализовать Polygon::format
и переопределить его в производных классах.
В сторону, используя friend
все равно несет запах кода. В целом считается, что лучше придать вашему классу открытый интерфейс, достаточно полный, чтобы внешнему коду не требовался привилегированный доступ для работы с ним.
Дальнейшее отступление для справочной информации: многократная отправка это вещь, и разрешение перегрузки C ++ прекрасно справляется, когда известны все типы аргументов статически. Не обрабатывается обнаружение динамического типа каждого аргумента во время выполнения и затем пытаясь найти лучшую перегрузку (что, если вы рассматриваете множественные иерархии классов, очевидно, нетривиально).
Если вы замените свой полиморфный список времени выполнения на полиморфный кортеж во время компиляции и выполните итерации тот, Ваша оригинальная схема перегрузки будет отправлена правильно.
Оператор не является виртуальным участником. Это означает, что невозможно отправить его производному классу. Только виртуальные функции могут отправлять динамически. Типичная стратегия в этом сценарии заключается в создании обычного оператора, который отправляет виртуальную функцию на интерфейсе для выполнения работы.
Кстати, в качестве дополнительного бонуса, new
это довольно бесполезная языковая функция в C ++. Вам нужно найти умные указатели, в противном случае каждую строку кода, которую вы пишете, вам просто придется переписать для бесконечных проблем на протяжении всей жизни.
Как правило, делать виртуальные операторы — довольно плохая идея. Это потому, что вы можете только динамически отправлять на this
но операторы часто используются с типом, реализующим их в RHS, или в качестве нечленов. Перегрузки операторов, не являющихся членами, являются более мощными, чем члены.
Вы можете добавить виртуальную функцию Display () в базовый класс Rectangle. Каждый класс в иерархии может переопределять функцию и реализовывать ее по-своему.
Вам нужно только определить один оператор<< это занимает многоугольник& в качестве параметра. Сама функция просто вызывает функцию виртуального дисплея.
class Polygon
{
public:
virtual void Display(ostream& os) const
{
os << "Polygon" << endl;
}
};
class Rectangle : public Polygon
{
public:
virtual void Display(ostream&os) const override
{
os << "Rectangle" << endl;
}
};
ostream& operator<<(ostream& os, const Polygon& p)
{
p.Display(os);
return os;
}