6

背景资料

我已经用 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!
4

3 回答 3

5

有没有办法在层次结构中重载运算符,导致我在这里做什么?

不。

问题是,运算符不在您的层次结构中这里的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++ 重载解析可以很好地处理它。没有处理的是在运行时发现每个参数的动态类型,然后尝试找到最佳重载(如果您考虑多个类层次结构,这显然是不平凡的)。

如果您将运行时多态列表替换为编译时多态元组并对其进行迭代,那么您的原始重载方案将正确分派。

于 2014-11-19T19:37:59.470 回答
3

操作员不是虚拟成员。这意味着它不可能分派给派生类。只有虚函数可以动态调度。在这种情况下,一个典型的策略是创建一个普通的操作符,它分派到接口上的一个虚函数来执行工作。

顺便说一句,作为一个额外的好处,new是 C++ 中一个非常无用的语言特性。您需要发现智能指针,否则您编写的每一行代码都必须重新编写,以解决无休止的生命周期问题。

通常,将操作员虚拟化是一个非常糟糕的主意。这是因为您只能动态调度,this但运算符经常与在 RHS 上实现它们的类型一起使用,或者作为非成员使用。非成员运算符重载比成员更强大。

于 2014-11-19T19:34:33.070 回答
1

您可以在基类 Rectangle 中添加一个虚拟 Display() 函数。层次结构中的每个类都可以覆盖该函数并以不同的方式实现它。

然后,您只需要定义一个以 Polygon& 作为参数的 operator<<。该函数本身只是调用虚拟显示函数。

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;
}
于 2014-11-19T19:41:15.677 回答