【问题标题】:Polymorphism in C++ vs JavaC++ 与 Java 中的多态性
【发布时间】:2014-11-24 15:46:20
【问题描述】:

我正在将一些 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 代码相同的结果?谢谢。

【问题讨论】:

  • “我正在将一些 Java 代码转换为 C++,我希望保持类结构相似。”这是一个坏主意。
  • Java 不是 C++,一般来说,利用您对外观相似的语言的了解来编写 C++ 代码并不是一个好主意。仅仅因为一个看起来像另一个并不意味着您可以随意进行逐行编码更改。从答案中可以看出,规则是不同的。您还缺少基类的虚拟析构函数,更不用说在您的示例中存在内存泄漏(在 C++ 中使用 new 与 Java 不同)。

标签: java c++ polymorphism factory


【解决方案1】:

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

在 Java 中,显然总是调用子类覆盖,即使在构造期间也是如此,因此 Java 中的构造函数从不调用可覆盖的方法。这样做会导致未定义的行为。这个here有一个很好的解释。

【讨论】:

  • 请注意,这也是 Java 中的代码异味。
【解决方案2】:

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

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

【讨论】:

  • 即使被覆盖是虚的,c++还是在构造函数中调用super的方法?听起来很奇怪。
【解决方案3】:

您不应该从基类构造函数调用虚函数 - 派生类的 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();
}

【讨论】:

  • 要挑剔,关于“派生类的 vtable 还没有被构造” - 这通常由编译器每个类完成一次;问题是正在构造的对象还没有链接到(指向)派生类 vtable。
  • @TonyD 什么是更好的词...更新?分配正确吗?
  • "linked to" 或 "known to" 也许,但很难在保持相同的通用措辞的同时使其读起来很好。我怀疑OP也会知道vtables。我不擅长这个,并且试图做一个彻底的工作最终得到这个:“......基类构造函数;基类子对象通常包含一个指向函数指针表(虚拟调度表,VDT)的指针,它实现虚函数,但是当基类构造函数运行时,基类构造函数表运行,正在构造的对象将指向基类VDT; ...
  • ...当派生类对象的其他基类和成员被构造时,它是派生类构造函数用指向它自己的 VDT 的指针替换该指针,因此将对象作为实例进行操作是安全的派生类型的。” 随意使用或修改,或者让人们在 cmets 中阅读它,如果更容易的话......干杯。
  • 至少 C++ 的行为看起来很正常——在基类的构造过程中,对象是一个基对象。调用虚函数仍然很臭,但至少它看起来内部一致并且偶尔有用。 Java 行为是未定义行为的秘诀。
【解决方案4】:

在 C++ 中,从基本构造函数调用虚函数不会调用更派生的实现。这样做的原因是,对于类型为BASE 的构造函数,类型是BASE,即使是从派生类DERIVED 调用构造函数。因此,虚函数表仍在构建中,直到 DERIVED 构造函数完成执行后才会指向更派生的实现。

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

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

【讨论】:

    【解决方案5】:

    当你创建一个 Derived 对象时,它首先调用 Base 的构造函数。在执行 Base 的构造函数(Mother)时,this 对象还不是 Derived(daughter) 类型;它的类型仍然只是 Base (Mother) 阅读本文了解更多信息:http://www.parashift.com/c%2B%2B-faq-lite/calling-virtuals-from-ctors.html

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-10-21
      • 2011-02-16
      • 1970-01-01
      • 1970-01-01
      • 2012-12-23
      • 1970-01-01
      • 2012-06-14
      相关资源
      最近更新 更多