2010-05-24 3 views
20

Я попытался вызвать переопределенный метод из конструктора родительского класса и заметил различное поведение на разных языках.Вызов переопределенного метода из родительского класса ctor

C++ - эхо A.foo()

class A{ 

public: 

    A(){foo();} 

    virtual void foo(){cout<<"A.foo()";} 
}; 

class B : public A{ 

public: 

    B(){} 

    void foo(){cout<<"B.foo()";} 
}; 

int main(){ 

    B *b = new B(); 
} 

Java - эхо B.foo()

class A{ 

    public A(){foo();} 

    public void foo(){System.out.println("A.foo()");} 
} 

class B extends A{ 

    public void foo(){System.out.println("B.foo()");} 
} 

class Demo{ 

    public static void main(String args[]){ 
     B b = new B(); 
    } 
} 

C# - эхо B.foo()

class A{ 

    public A(){foo();} 

    public virtual void foo(){Console.WriteLine("A.foo()");} 
} 

class B : A{  

    public override void foo(){Console.WriteLine("B.foo()");} 
} 


class MainClass 
{ 
    public static void Main (string[] args) 
    { 
     B b = new B();    
    } 
} 

Я понимаю, что в C++ объекты создаются из самого верхнего родителя, идя вниз по иерархии, поэтому, когда конструктор вызывает переопределенный метод, B даже не существует, поэтому он вызывает версию A этого метода. Тем не менее, я не уверен, почему у меня другое поведение в Java и C# (из C++)

+5

Не вызывайте виртуальную функцию в конструкторе C++ ... http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.7 –

+5

Вопрос - странный вопрос. C# и C++ - разные языки, поэтому, конечно, у них разные правила. Если бы они имели те же правила *, то они были бы * одним и тем же языком *. Почему * должен * C# следовать правилам C++? –

+0

Связанное сообщение my на конструкторах Java и шаблоне шаблона шаблона: http://novyden.blogspot.com/2011/08/java-anti-pattern-constructors-and.html – topchef

ответ

25

В C++, как вы правильно отметили, объект имеет тип A до тех пор, пока конструктор A не будет закончен. Объект фактически меняет тип во время его построения. Вот почему используется vtable класса A, поэтому A::foo() вызывается вместо B::foo().

В Java и C# виртуальная таблица (или эквивалентный механизм) самого производного типа используется повсюду даже при построении базовых классов. Поэтому на этих языках вызывается B.foo().

Обратите внимание, что обычно не рекомендуется вызывать виртуальный метод из конструктора. Если вы не очень осторожны, виртуальный метод может предположить, что объект полностью сконструирован, хотя это не так. В Java, где каждый метод неявно виртуальный, у вас нет выбора.

+3

Вы можете пометить методы как 'final' в Java, что предотвращает их переопределение подклассами. Я считаю, что в C# есть аналогичный механизм (возможно, «запечатан»?). –

+0

Да, методы маркировки, которые (прямо или косвенно) вызваны из конструктора как 'final', были бы мудрыми. В C# нет необходимости в аналогичном механизме, потому что в C# методы являются только «виртуальными», если вы их явно объявляете. – Thomas

+1

«В Java и C# используется vtable (или эквивалентный механизм) самого производного типа» - не могли бы вы объяснить, я знаю, что такое vtable. –

9

Хотя я понимаю, что вы делаете это для экспериментов, важно отметить следующую цитату из Эффективного Java 2nd Edition, Пункт 17: Дизайн и документ для наследования, либо запретить ему:

Есть еще несколько ограничений, которые должен выполнять класс для разрешения наследования. Конструкторы не должны вызывать чрезмерные методы, прямо или косвенно. Если вы нарушите это правило, произойдет сбой программы. Конструктор суперкласса выполняется перед конструктором подкласса, поэтому метод переопределения в подклассе будет вызываться до запуска конструктора подкласса. Если метод переопределения зависит от любой инициализации, выполняемой конструктором подкласса, метод не будет вести себя так, как ожидалось.

Вот пример для иллюстрации:

public class ConstructorCallsOverride { 
    public static void main(String[] args) { 
     abstract class Base { 
      Base() { overrideMe(); } 
      abstract void overrideMe(); 
     } 
     class Child extends Base { 
      final int x; 
      Child(int x) { this.x = x; } 
      @Override void overrideMe() { 
       System.out.println(x); 
      } 
     } 
     new Child(42); // prints "0" 
    } 
} 

Здесь, когда Base конструктор вызывает overrideMe, Child не закончил инициализацию final int x, а метод получает неверное значение. Это почти наверняка приведет к ошибкам и ошибкам.

+0

«поэтому будет вызван метод переопределения в подклассе» - как суперкласс в процессе его построения даже вызывает метод подкласса (когда подкласс еще предстоит построить)? (Я понял пример выше, что инициализация подкласса не была выполнена, но я надеялся, что метод не должен был даже вызываться, потому что подкласс еще не создан) –

+0

Ваш вопрос основан на ошибочном предположении, что метод нельзя вызвать без создания объекта производного класса. В Java это возможно. – klaar

Смежные вопросы