无法理解C++派生

dun*_*cks 4 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;
}
Run Code Online (Sandbox Code Playgroud)

当我运行这个程序时,输出是Piece,但我希望看到Man.这叫做运行时多态吗?我认为那是保留给功能的,但我不知道.Java中的"等效"程序打印出我期望的内容,Man但我不知道如何让这个C++程序做到这一点.我错过了什么?

Die*_*ühl 6

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

Square(Piece piece)
Run Code Online (Sandbox Code Playgroud)

通过值获取它的参数,它将始终是类型Piece而不是任何派生类型:参数,如果它是派生类型,则被切片.您可以使用类似的表示法通过引用传递对象

Square(Piece& piece)
Run Code Online (Sandbox Code Playgroud)

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

Square(Piece const& piece)
Run Code Online (Sandbox Code Playgroud)

如果引用的对象piece应该是不可变的.在您的环境下,你最有可能也想对付你的对象的生命时间管理这可能是最好的使用上使用堆分配的对象做new一些智能指针,如性病:: 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
    }
};
Run Code Online (Sandbox Code Playgroud)

使用此设置,您可以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();
}
Run Code Online (Sandbox Code Playgroud)

的有点奇特,向前print()do_print()是不是真正需要的,但如果你有多个virtual具有相同名称的超载和重写只是其中之一,其他所有的版本在基类中获得隐藏.由于print()没有被覆盖并且do_print()没有被用户调用,因此隐藏重载的问题有所减少.


Ern*_*ill 5

这都是关于"静态多态",也就是方法重载.operator<<编译器根据它看到的变量的编译时类型选择特定版本; 因为get_piece()返回Piece,operator<<所以选择了它的版本.

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