无法理解C ++派生

时间:2013-08-18 00:11:30

标签: c++

我带来了很多来自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 ++程序来做到这一点。我错过了什么?

2 个答案:

答案 0 :(得分:6)

与Java C ++不同,它区分对象和值的引用,即对象本身。当您将派生类型的对象传递给采用基类型值的函数时,您将获得切片对象:它将只包含对象基本部分的副本,而不包含派生类型的副本。例如,您的构造函数

Square(Piece piece)

按值获取其参数,它将始终为Piece类型,而不是任何派生类型:参数(如果它是派生类型)被切片。您可以使用

之类的表示法通过引用传递对象
Square(Piece& piece)

如果piece引用的对象是可变的或

Square(Piece const& piece)

如果piece引用的对象应该是不可变的。在您的上下文中,您很可能还希望处理对象的生命周期管理,这可能最好使用new上使用virtual在堆上分配的对象来完成,并由一些智能指针维护,例如std :: shared_ptr`

现在开始输出函数:被调用的函数总是基于静态类型静态解析,即在编译期间声明和可见的类型。一旦调用了正确的函数,如果它恰好被声明为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()

do_print()virtual的有点古怪前锋并不是真的需要,但如果你有多个print()重载同名并且你只覆盖其中一个,那么所有其他基类中的版本被隐藏。由于do_print()未被覆盖且用户未调用{{1}},因此隐藏重载的问题会有所减少。

答案 1 :(得分:5)

这完全是关于“静态多态”,即方法重载。编译器根据它看到的变量的编译时类型选择operator<<的特定版本;自get_piece()返回Piece后,就会选择operator<<的版本。

我必须指出你对等效的Java程序是错误的:Java方法重载也由编译器仲裁,并且基于编译时类型。一个真正等效的Java程序也会显示Piece