理解C++派生有困难
Trouble understanding C++ derivation
我将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++程序做到这一点。我错过了什么?
与Java C++不同,区分对对象和值的引用,即对象本身。当您将派生类型的对象传递给采用基类型值的函数时,您会得到一个切片的对象:它只包含对象基部分的副本,而不包含派生类型的任何内容。例如,您的构造函数
Square(Piece piece)
按值获取其参数,并且它将始终为Piece
类型,而从不为任何派生类型:如果参数为派生类型,则会对其进行切片。您可以使用类似的符号通过引用传递对象
Square(Piece& piece)
如果piece
引用的对象是可变的或
Square(Piece const& piece)
如果CCD_ 6引用的对象应该是不可变的。在您的上下文中,您很可能还想处理对象的生命周期管理,这可能最好使用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()
到do_print()
的某种奇怪的转发并不是真正需要的,但如果您有多个具有相同名称的virtual
重载,并且只覆盖其中一个重载,则基类中的所有其他版本都会被隐藏。由于print()
没有被覆盖,do_print()
也没有被用户调用,所以隐藏重载的问题在一定程度上减少了。
这都是关于"静态多态性",也就是方法重载。operator<<
的特定版本由编译器根据其看到的变量的编译时类型来选择;由于get_piece()
返回Piece
,因此这是所选择的operator<<
的版本。
我必须指出,您对等效Java程序的看法是错误的:Java方法重载也是由编译器仲裁的,并且是基于编译时类型的。一个真正等效的Java程序也会显示Piece
。
- 为什么使用 "this" 指针调用派生成员函数?
- 具有奇怪重复模板模式的派生类中的成员变量已损坏
- 在派生函数中指定void*参数
- 如何通过派生类函数更改基类中的向量
- 如何委托派生类使用其父构造函数?
- 如何使用单独文件中的派生类访问友元函数对象
- 派生类销毁的最佳实践是什么
- 如何使用基类指针引用派生类成员
- 派生类是否可以在抽象工厂设计模式中具有数据成员
- 使用基类指针创建对象时,缺少派生类析构函数
- 如何引用基类的派生类?
- 存储模板类型以强制转换回派生<T>
- 需要从 istream 和 ostream 派生 iostream
- 在 C++ 中用派生类型重写成员函数
- 具有多个类、派生类的C++正向声明
- 有没有一种"cleaner"的方法可以在指向基的指针向量中找到派生类的第一个实例?
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 如果基类包含双指针成员,则派生类的构造函数
- 为什么此派生对象无法访问基类的后递减方法?
- 有没有比在库中添加一个并非由所有派生类实现的新虚拟函数更好的设计实践