C++ vs Java 中的多态性

Polymorphism in C++ vs Java

本文关键字:多态性 Java vs C++      更新时间:2023-10-16

我正在将一些Java代码转换为C++,我想保持类结构相似。但是,我遇到了以下问题,我不知道如何解决;我在Java中这样做:

public class Mother {   
    protected Father make;  
    public  Mother(){       
        make = maker();         
        make.print(); };    
    public Father maker(){ 
        return new Father();};}
public class Daughter extends Mother {
    public Daughter(){
        super();}
    @Override
    public Father maker(){
        return new Son();};}
public class Father {
    public void print(){
        System.out.println("I am the Father!n");}}
public class Son extends Father {
    @Override
    public void print(){
        System.out.println("I am the son!n");};}
public static void main(String[] args) {
    Daughter dot  = new Daughter();
}

将产生:我是儿子!而:

class father{
public:
    virtual void print(){
        std::cout << "I am the father!n";
}; };
class son: public father{
public:
    virtual void print(){
        std::cout << "I am the son!n";
    };};
class mother{
protected:
    father *make;
public:
    mother(){
        make = maker();
        make->print();
    };
    virtual father *maker(){
        return new father();
    };};
class daughter: public mother{
public:
    daughter(): mother() {
    };
    virtual father *maker(){
        return new son();
    };};

int main(int argc, const char * argv[]) {
    daughter *d = new daughter();

将产生我是父亲!如何使C++代码产生与 Java 代码相同的结果?谢谢。

Daughter 的构造函数调用Mother构造函数,构造函数调用maker()。至少在C++中,由于Daughter构造不完整,因此此时该对象仅被视为Mother。因此Mother::maker()被调用,所以这是在做正确的事情。但是,在构造过程中调用虚拟函数通常被认为是一种强烈的代码气味 - 正是出于这些原因。

在 Java 中,显然子类覆盖总是被调用,即使在构造过程中也是如此,因此 Java 中的构造函数永远不应该调用可重写的方法。这样做可能会导致未定义的行为。这里有一个非常好的解释。

Java 中的 AFAIK 在构造函数中调用(非最终)方法只是不好的风格,即可以在派生类中重写的方法。 C++总是调用实际类的版本,而不是被覆盖的版本。

你能通过将对象传递给构造函数来解决这个问题吗?

你不应该从基类构造函数调用虚函数 - 派生类的 vtable 还没有链接到,所以你总是会结束调用基类的函数。你也不应该在 Java 中这样做,因为虽然它会调用正确的函数,但派生最多的类还没有被实例化 - 这可能会导致未定义的行为。最终,由于不同的原因,这两种语言都是错误的。

解决此问题的一种方法是让派生类将虚拟调用的结果传递到基类中:

daughter(): mother(new son) { }

因此:

mother() : make(new father) { make->print(); }
mother(father * m) : make(m) { make->print(); }

通过委派构造函数,这变得更容易:

mother()
: mother(new father)
{ }
mother(father* m)
: make(m)
{
    make->print();
}

C++从基构造函数调用虚拟函数不会调用派生的实现。这样做的原因是,对于类型BASE的构造函数,类型是BASE的,即使构造函数是从派生类调用的,DERIVED .因此,虚函数表仍在构造中,并且在DERIVED构造函数完成执行之前不会指向派生的更多实现。

Java(和 C#)与这里的C++不同,因为您可以从基本构造函数调用虚拟函数,并且它将调用派生最多的实现。但是,由于派生最多的构造函数尚未运行,因此对象可能处于未定义状态,这就是为什么不建议从构造函数调用虚拟函数的原因。

至于如何绕过它,您可以添加一个在创建实例后调用的initialize方法。由于此时对象将被完全构造,它将调用正确的虚函数(在所有语言中)。

当你创建一个派生对象时,它首先调用 Base 的构造函数。执行 Base 的构造函数 (Mother) 时,this 对象还不是 Derived(daughter) 类型;它的类型仍然只是基地(母亲)阅读此内容以获取更多信息:http://www.parashift.com/c%2B%2B-faq-lite/calling-virtuals-from-ctors.html