Вызов переопределенного метода из родительского класса ctor - PullRequest
21 голосов
/ 24 мая 2010

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

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 ++)

Ответы [ 2 ]

26 голосов
/ 24 мая 2010

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

вызывается A::foo().

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

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

9 голосов
/ 24 мая 2010

Хотя я понимаю, что вы делаете это для экспериментов, важно отметить следующую цитату из Effective Java 2nd Edition, Item 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, и метод получает неправильное значение. Это почти наверняка приведет к ошибкам и ошибкам.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...