4

私は Java から C++ の学習に多くの仮定を持ち込んでいますが、これがまた私を困惑させているようです。私は、次のプログラムから期待されることを雄弁に語れる語彙を持っていません。

#include <iostream>
#include <vector>

using namespace std;

class Piece {};

class Man : public Piece {};

class Square {
  Piece p;
public:
  Square(Piece p) : p(p) { };
  Piece get_piece() const { return p; }
};

ostream& operator<<(ostream& os, const Piece& p)
{
  cout << "Piece";
  return os;
}

ostream& operator<<(ostream& os, const Man& m)
{
  cout << "Man";
  return os;
}

ostream& operator<<(ostream& os, const Square& s)
{
  cout << s.get_piece() << '\n';
  return os;
}

int main()
{
  Square sq = Square(Man());
  cout << sq;
}

このプログラムを実行すると、出力はPieceになりますが、期待していたのはManです。これは実行時ポリモーフィズムと呼ばれますか? 関数用に予約されていると思いましたが、わかりません。Javaの「同等の」プログラムは、私が期待するものを出力しますManが、このC++プログラムでそれを行う方法がわかりません。私は何が欠けていますか?

4

2 に答える 2

6

Java C++ とは異なり、オブジェクトへの参照と値、つまりオブジェクト自体を区別します。基本型の値を受け取る関数に派生型のオブジェクトを渡すと、スライスされたオブジェクトが得られます。これには、オブジェクトの基本部分のコピーのみが含まれ、派生型は含まれません。たとえば、コンストラクタ

Square(Piece piece)

引数を値で受け取り、それは常に型Pieceであり、派生型ではありません: 引数が派生型である場合、スライスされます。次のような表記法を使用して、参照によってオブジェクトを渡すことができます

Square(Piece& piece)

によって参照されるオブジェクトpieceが可変である場合、または

Square(Piece const& piece)

によって参照されるオブジェクトpieceが不変である必要がある場合。あなたのコンテキストでは、おそらくnewstd::shared_ptr` などのスマート ポインターを使用してヒープに割り当てられ、維持されるオブジェクトを使用して、オブジェクトのライフタイム管理を処理することをお勧めします。

次に、出力関数について説明します。呼び出される関数は、静的な型、つまりコンパイル時に宣言され可視化される型に基づいて、常に静的に解決されます。正しい関数が呼び出されると、それが宣言virtualされている場合は、オブジェクトの動的な型に基づいて可能なオーバーライド関数にディスパッチされます。つまり、仮想ディスパッチは実行時に行われます。出力演算子の目的のために、これは静的型に基づいてのみ選択されることを意味します。あなたの場合は常にPieceです。これに対処する通常の方法は、関数を使用virtualし、実際の出力演算子からこの関数にディスパッチすることです。次に例を示します。

class Piece {
protected:
    virtual std::ostream& do_print(std::ostream& out) const  = 0;
public:
    std::ostream& print(std::ostream& out) const { return this->do_print(out); }
};
std::ostream& operator<< (std::ostream& out, Piece const& piece) {
    return piece.print(out);
}

class Man: public Piece {
protected:
    std::ostream& do_print(std::ostream& out) {
        return out << "Man";  // note: you want to use out, not std::cout here
    }
};

このセットアップPieceでは、このタイプのオブジェクトへの参照を使用して静的出力演算子を呼び出し、動的タイプによって選択された出力を取得できます。

class Square {
    std::shared_ptr<Piece> d_piece;
public:
    Square(std::shared_ptr<Piece> piece): d_piece(piece) {}
    Piece const& get_piece() const { return *this->d_piece; }
};
std::ostream& operator<< (std::ostream& out, Square const& square) {
    return out << square.get_piece();
}

print()toの多少風変わりな転送は実際にdo_print()は必要ありませんがvirtual、同じ名前のオーバーロードが複数あり、そのうちの 1 つだけをオーバーライドすると、基本クラスの他のすべてのバージョンが非表示になります。print()はオーバーライドされておらず、ユーザーによって呼び出されていないためdo_print()、オーバーロードを隠す問題は多少軽減されます。

于 2013-08-18T00:33:46.320 に答える
5

これはすべて、「静的ポリモーフィズム」、つまりメソッドのオーバーロードに関するものです。の特定のバージョンはoperator<<、コンパイラが参照する変数のコンパイル時の型に基づいてコンパイラによって選択されます。get_piece()を返すので、それが選択されPieceたバージョンです。operator<<

同等のJavaプログラムについてあなたが間違っていることを指摘しなければなりません.Javaメソッドのオーバーロードもコンパイラによって調停され、コンパイル時の型に基づいています。真に同等の Java プログラムも同様に表示Pieceされます。

于 2013-08-18T00:30:42.370 に答える