Java C++ とは異なり、オブジェクトへの参照と値、つまりオブジェクト自体を区別します。基本型の値を受け取る関数に派生型のオブジェクトを渡すと、スライスされたオブジェクトが得られます。これには、オブジェクトの基本部分のコピーのみが含まれ、派生型は含まれません。たとえば、コンストラクタ
Square(Piece piece)
引数を値で受け取り、それは常に型Piece
であり、派生型ではありません: 引数が派生型である場合、スライスされます。次のような表記法を使用して、参照によってオブジェクトを渡すことができます
Square(Piece& piece)
によって参照されるオブジェクトpiece
が可変である場合、または
Square(Piece const& piece)
によって参照されるオブジェクトpiece
が不変である必要がある場合。あなたのコンテキストでは、おそらくnew
std::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()
、オーバーロードを隠す問題は多少軽減されます。