Здравствуйте, <Аноним>, Вы писали:
А>Может и простенький вопрос, но мне не понятно: А>чем плох вызов виртуального метода из конструктора?
Эта проблема называется "потерей указателя this". В наследнике переопределенный метод может обратиться к полю класса (this.*), считая, что совершает вполне легальное действие, в то время как конструктор, быть может, еще не успел инициализировать данное поле (в зависимости от кода конструктора) — и вылетит исключение обращения по пустой ссылке, к примеру. В общем случае конструктор не должен "терять this" (делать его доступным кому-либо) вплоть до своего завершения.
Здравствуйте, Аноним, Вы писали:
А>Привет всем.
А>Может и простенький вопрос, но мне не понятно: А>чем плох вызов виртуального метода из конструктора?
А>Спасибки
Скорее всего, дело в том, что пока не выполнились все конструкторы, объект полностью не сконструирован, а следовательно таблица виртуальных функций (или что там в net framework) не полностью загружена в память, и виртуальность не может быть корректно разрешена, по крайней мере так дело обстоит с С++.
Здравствуйте, denezuela, Вы писали:
А>>Может и простенький вопрос, но мне не понятно: А>>чем плох вызов виртуального метода из конструктора?
D>Скорее всего, дело в том, что пока не выполнились все конструкторы, объект полностью не сконструирован, а следовательно таблица виртуальных функций (или что там в net framework) не полностью загружена в память, и виртуальность не может быть корректно разрешена, по крайней мере так дело обстоит с С++.
Здесь не так Тут именно что поля могут быть ещё непроинициализированы.
Help will always be given at Hogwarts to those who ask for it.
Re[3]: Virtual member call in constructor. Плохо?
От:
Аноним
Дата:
30.10.07 15:02
Оценка:
Здравствуйте, _FRED_, Вы писали:
_FR>Здесь не так Тут именно что поля могут быть ещё непроинициализированы.
А разве инициализация полей дефолтовыми значениями происходит не до выполнения конструктора?
Здравствуйте, Аноним, Вы писали: А>А разве инициализация полей дефолтовыми значениями происходит не до выполнения конструктора?
Происходит. Вопрос не этом, а в том, что возможна производному классу нужна более сложная инициализация, которую выполняет еще не вызванный конструктор.
Исходя из статьи следует, что проблемы могут возникнуть только при многопоточности.
Я уже понял, что это плохая практика, но всё же интересно: однопоточному приложению это ничем не грозит?
Здравствуйте, Mab, Вы писали:
Mab>Здравствуйте, Аноним, Вы писали: А>>А разве инициализация полей дефолтовыми значениями происходит не до выполнения конструктора? Mab>Происходит. Вопрос не этом, а в том, что возможна производному классу нужна более сложная инициализация, которую выполняет еще не вызванный конструктор.
Можно ли тогда сказать, что вызов виртальных методов в конструкторе, не есть плохо, но к этому надо подходить внимательно?
Здравствуйте, <Аноним>, Вы писали:
А>Исходя из статьи следует, что проблемы могут возникнуть только при многопоточности.
Не следует, прочитайте внимательнее.
А>Я уже понял, что это плохая практика, но всё же интересно: однопоточному приложению это ничем не грозит?
Грозит.
Здравствуйте, vladpol, Вы писали:
V>Можно ли тогда сказать, что вызов виртальных методов в конструкторе, не есть плохо,
Имхо, есть, но этого часто попросту не избежать (изменение свойства "Property" формы в InitializeComponent часто приводит к вызову виртуального метода "OnPropertyChanged" :о()
V>но к этому надо подходить внимательно?
Да
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, vladpol, Вы писали:
V>Можно ли тогда сказать, что вызов виртальных методов в конструкторе, не есть плохо, но к этому надо подходить внимательно?
ИМХО да.
Здравствуйте, <Аноним>, Вы писали:
А>А разве инициализация полей дефолтовыми значениями происходит не до выполнения конструктора?
Не до завершения родительского конструктора.
Здравствуйте, vladpol, Вы писали:
V>Можно ли тогда сказать, что вызов виртальных методов в конструкторе, не есть плохо, но к этому надо подходить внимательно?
Это есть плохо и надо выжигать каленым железом. Сколь ни подходи внимательно, если указатель this опубликован, то... кто-нибудь да когда-нибудь его попытается дернуть раньше времени. И это может сделать вовсе не сам внимательный автор класса, не сам внимательный автор всей библиотеки, не самый внимательнейший автор движка или системы — а кто-то, кто не знает, о том что все они (а кое-то из них по-дурости совершенно сознательно), даже будучи внимательными, заложили в свою систему риск безопасности инициализации.
Это как ружье, что висит на стене в первом акте спектакля...
Толку от этого — чуть, часто проблемы это вызывает именно во всяких "компонентах", свойства которых любят отправлять события об их, свойств, изменении, а в производных классах свойства меняются и события "отлавливаются"
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, Аноним, Вы писали:
А>Привет всем.
А>Может и простенький вопрос, но мне не понятно: А>чем плох вызов виртуального метода из конструктора?
Плох тем, что класс-наследник может обратиться к чему угодно в этом переопределённом методе, в т.ч. к своим еще непроинициализированным полям.
Тем не менее, дизайнеры форм и компонентов генерят метод InitializeComponent, который вызывается из конструктора, и который, в свою очередь, вызывает тонны виртуальных методов или обращается к виртуальным св-вам. Об этом надо помнить, при переопределении виртуальных св-в у Control и его наследников. Иногда я прибегаю к услугам ISupportInitialize, как к более общему решению (которое накрывает и эту проблему в т.ч.)
Здравствуйте, igna, Вы писали: I>А польза какая от решения принятого в C#, ну то есть от того, что конструктор базового класса вызывает виртуальную функцию производного класса?
Никакой специальной пользы нет, как и вреда.
Просто другая архитектура. Тип объекта никогда не меняется в течение его жизни. А в С++ объект при создании/разрушении проходит всю эволюционную цепочку. В управляемой среде так делать просто нельзя — например, если в конструкторе какого-либо из классов в цепочке наследования произойдет исключение, тип объекта должен быть правильным.
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Там написано "...виртуальность не может быть корректно разрешена, по крайней мере так дело обстоит с С++", а это неверно. В C++ виртуальность разрешается в пользу функции базового класса.
I>Там написано "...виртуальность не может быть корректно разрешена, по крайней мере так дело обстоит с С++", а это неверно. В C++ виртуальность разрешается в пользу функции базового класса.
Ну вот с точки зрения .NET это некорректное поведение
Здравствуйте, igna, Вы писали: I>Ну да. А корректное с точки зрения .NET поведение приводит к появлению советов вроде "Do not call overridable methods in constructors".
Позволю себе напомнить, что вызов виртуальных методов из конструкторов в C++ также является порочной практикой.
И ровно по той причине, что виртуальность работает не так, как в других методах. Так что хрен редьки не слаще.
Классической задачкой на понимание неочевидностей процесса конструирования объекта в C++ является вопрос "можно ли произвести вызов абстрактного метода без хаков с кастом?".
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, rsn81, Вы писали:
R>Здравствуйте, <Аноним>, Вы писали:
А>>А разве инициализация полей дефолтовыми значениями происходит не до выполнения конструктора? R>Не до завершения родительского конструктора.
след. пример показывает, что вы ошибаетесь:
class Program
{
static void Main(string[] args) {
new Derived();
Console.ReadLine();
}
}
class Base
{
public Base() {
Console.WriteLine(string.Format("i = {0}", this.GetType().GetField("i").GetValue(this)));
Console.WriteLine("Base .ctor end");
}
}
class Derived : Base
{
public int i = 3;
Здравствуйте, vensub, Вы писали:
V>след. пример показывает, что вы ошибаетесь:
[skipped]
Интересно. Гм... и странно, если ничего не напутали. Под рукой студии нет, проверил тоже самое в Java:
package ru.rsdn.init;
public class Base {
public Base() {
try {
System.out.println("base.ctor: " +
getClass().getField("i").getInt(this));
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
package ru.rsdn.init;
public class Derived extends Base {
public final int i = 1;
public Derived() {
System.out.println("derived.ctor: " + i);
}
public static void main(String... args) {
new Derived();
}
}
Здравствуйте, rsn81, Вы писали:
R>Интересно, ведь получается, что в .NET таким образом автоматически частично решается обсуждаемая в теме проблема...
Частично -- да, если инициализация производного класса совсем простая. Но одновременно добавляется тонкий аспект поведения, о котором нужно помнить.
Re[12]: Virtual member call in constructor. Плохо?
Здравствуйте, Mab, Вы писали:
Mab>Частично -- да, если инициализация производного класса совсем простая. Но одновременно добавляется тонкий аспект поведения, о котором нужно помнить.
Смотрю сейчас язык Scala под JVM. Вот такой класс: