6

背景情報

私はしばらく Java でプログラミングをしてきましたが、C++ に切り替えたのはほんの数か月前です。以上で、いよいよ本題です!私は基本的なテキスト ベースのゲーム エンジンを開発していますが、最近、興味深い具体的でありそうもない問題に遭遇しました。以下のプログラムで小規模にテストしてみましたが、(実際のゲーム コードとは対照的に) 画面を詰まらせず、問題の複雑さを軽減するために、(実際のゲーム コードとは対照的に) 表示することにしました。以下にモデル化された問題は、私の実際のコードの問題を反映していますが、ふわふわした邪魔者はありません。

問題

本質的に、問題はポリモーフィズムの 1 つです。出力演算子「<<」をオーバーロードして、階層内の各オブジェクトに固有の表示関数として機能させたいと考えています。問題は、これらの階層メンバーを格納するリストからこの演算子を呼び出すと、ID が失われ、基本クラスの出力演算子が呼び出されることです。通常、オペレーターのオーバーロードを単純な表示メソッドに置き換え、表示メソッドを仮想としてマークし、幸せな一日に進むことでこれを解決します。コードを変更することは特に気にしませんが、今は単純に興味があります。私がここで行っていることになる階層内の演算子をオーバーロードする方法はありますか?

【例】コード

#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&);

(明らかに、ここでは 2 番目のケースは存在しません。変更する必要があるためです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 に答える
1

基本クラス Rectangle に仮想 Display() 関数を追加できます。階層内の各クラスは、関数をオーバーライドして、異なる方法で実装できます。

次に、Polygon& をパラメーターとして受け取る operator<< を 1 つ定義するだけで済みます。関数自体は、仮想表示関数を呼び出すだけです。

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 に答える