Полиморфные вызовы в конструкторе
От: Alexey Shirshov Россия http://wise-orm.com
Дата: 10.11.03 10:38
Оценка:
Всем привет!

В С++, как известно, виртуальность вирутальных функций при вызове их в конструкторе "не работает". Функция вызывается "напрямую", в обход виртуальной таблице.
В дотнете ситуация противоположенная: полиморфизм работает везде и даже в конструкторе. К чему это может привести? К тому, что некоторый код класса может быть вызван до того, как отработает конструктор данного экземпляра.

Прошу вас высказывать, не стесняться, с вои мысли по поводу того, насколько было верно решение проектировщиков дотнета, когда они отошли от принятого в С++ подхода.

Так же интересует, каковы плюсы полиморфизма всегда и везде и минусы решения С++. Возникали ли когда-нибудь у вас на этой почве проблемы?
... << RSDN@Home 1.1.0 stable >>
Re: Полиморфные вызовы в конструкторе
От: mrhru Россия  
Дата: 10.11.03 11:17
Оценка:
Здравствуйте, Alexey Shirshov, Вы писали:

AS>В С++, как известно, виртуальность вирутальных функций при вызове их в конструкторе "не работает". Функция вызывается "напрямую", в обход виртуальной таблице.

AS>В дотнете ситуация противоположенная: полиморфизм работает везде и даже в конструкторе. К чему это может привести? К тому, что некоторый код класса может быть вызван до того, как отработает конструктор данного экземпляра.

AS>Прошу вас высказывать, не стесняться, с вои мысли по поводу того, насколько было верно решение проектировщиков дотнета, когда они отошли от принятого в С++ подхода.


AS>Так же интересует, каковы плюсы полиморфизма всегда и везде и минусы решения С++. Возникали ли когда-нибудь у вас на этой почве проблемы?


Такое поведение сделано и в Delphi. Проблем не вызывает.
На самом деле, в момент вызова тела конструктора — объект уже "существует" (и даже инициализирован нулями).
Re[2]: Полиморфные вызовы в конструкторе
От: _wqwa США  
Дата: 10.11.03 11:30
Оценка:
Здравствуйте, mrhru, Вы писали:

M>Такое поведение сделано и в Delphi. Проблем не вызывает.

M>На самом деле, в момент вызова тела конструктора — объект уже "существует" (и даже инициализирован нулями).

"Существует" означает не только факт выделения памяти под объект, но еще и корректное его состояние.
Если не дожидаться отработки конструктора, состояние объекта может быть некорректным по логике работы системы.
Кто здесь?!
Re[2]: Полиморфные вызовы в конструкторе
От: WolfHound  
Дата: 10.11.03 11:32
Оценка:
Здравствуйте, mrhru, Вы писали:

M>Такое поведение сделано и в Delphi. Проблем не вызывает.

В дельфе нет конструкторов там есть фабричные функции что далеко не одно и тоже.
M>На самом деле, в момент вызова тела конструктора — объект уже "существует" (и даже инициализирован нулями).
И чем нули отличаются от мусора? Очень часто надо инициализировать значениями отличными от нуля.

ЗЫ Ой чую сейчас флейм начнется... А не переехоть ли нам от сюда?
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[3]: Полиморфные вызовы в конструкторе
От: WolfHound  
Дата: 10.11.03 11:34
Оценка: :)
Здравствуйте, WolfHound, Вы писали:

WH>ЗЫ Ой чую сейчас флейм начнется... А не переехоть ли нам от сюда?

Опс. Меня проглючило. Мне почемуто казалось что я С++ читаю
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[2]: Полиморфные вызовы в конструкторе
От: Alexey Shirshov Россия http://wise-orm.com
Дата: 10.11.03 11:38
Оценка:
Здравствуйте, mrhru, Вы писали:

хъ

M>Такое поведение сделано и в Delphi. Проблем не вызывает.

M>На самом деле, в момент вызова тела конструктора — объект уже "существует" (и даже инициализирован нулями).

А если для правильной работы функции нужна будет инициализация в конструкторе (который еще не вызван)?
... << RSDN@Home 1.1.0 stable >>
Re[3]: Полиморфные вызовы в конструкторе
От: mrhru Россия  
Дата: 10.11.03 11:46
Оценка:
Здравствуйте, _wqwa, Вы писали:

M>>Такое поведение сделано и в Delphi. Проблем не вызывает.

M>>На самом деле, в момент вызова тела конструктора — объект уже "существует" (и даже инициализирован нулями).

_>"Существует" означает не только факт выделения памяти под объект, но еще и корректное его состояние.


В данном случае, имхо, "существует" — это означает, что память выделена, физически инициализирована. т.е. гарантировано отсутствие мусора типа ссылок в "никуда", и работоспособен механизм наследования (раз объект уже существует)

_>Если не дожидаться отработки конструктора, состояние объекта может быть некорректным по логике работы системы.


По моему очень скромному имхо, в Delphi это сделано даже чуть лучше чем в С# — там возможно вызывать конструктор базового класса в любом месте конструктора текущего (а можно и забыть вызвать ). В этом смысле — для программиста — конструктор становится просто чем-то вроде функции Init. Это, как минимум удобно.
По-поводу корректного состояния — так для этого конструктор и предназначен, чтобы обеспечивать правильное логическое состояние.
Re[3]: Полиморфные вызовы в конструкторе
От: mrhru Россия  
Дата: 10.11.03 11:50
Оценка:
Здравствуйте, Alexey Shirshov, Вы писали:

M>>Такое поведение сделано и в Delphi. Проблем не вызывает.

M>>На самом деле, в момент вызова тела конструктора — объект уже "существует" (и даже инициализирован нулями).

AS>А если для правильной работы функции нужна будет инициализация в конструкторе (который еще не вызван)?


Так ведь нельзя получить ссылку на объект, не вызвав конструктора, т.е. для коде "извне" объекта — конструктор обязательно будет вызван до любого использования. А для кода "внутри" — память уже будет выделена и проинициализирована.
Re: Полиморфные вызовы в конструкторе
От: Кодт Россия  
Дата: 10.11.03 11:52
Оценка: 51 (10) +1
Здравствуйте, Alexey Shirshov, Вы писали:

AS>В С++, как известно, виртуальность вирутальных функций при вызове их в конструкторе "не работает". Функция вызывается "напрямую", в обход виртуальной таблице.

AS>В дотнете ситуация противоположенная: полиморфизм работает везде и даже в конструкторе. К чему это может привести? К тому, что некоторый код класса может быть вызван до того, как отработает конструктор данного экземпляра.

AS>Прошу вас высказывать, не стесняться, с вои мысли по поводу того, насколько было верно решение проектировщиков дотнета, когда они отошли от принятого в С++ подхода.


AS>Так же интересует, каковы плюсы полиморфизма всегда и везде и минусы решения С++. Возникали ли когда-нибудь у вас на этой почве проблемы?


Лично у меня — не возникало, потому что не использую C#.

Если же подойти к этому вопросу пристально, то конструирование объекта (по любой стратегии) состоит из:
* выделения памяти ( new Class )
* вызова конструкторов предков ( Class(ctor-params) : Parent(parent-ctor-params) )
* вызова конструкторов новых членов ( , member_(member-ctor-params) )
* выполнения тела конструктора ( { ..... } )
* дополнительных действий в клиентском коде ( Class* p = new Class(...); p->setup(...) )

Подход С++: последовательное конструирование от предка к потомку. На момент конструирования предка потомок заполнен мусором. Поэтому вызов перекрытой виртуальной фуниции из предка потенциально опасен. Следовательно, предок должен верить, что он конструирует финальный объект (что можно достичь, устанавливая vfptr на VMT предка).

Другой возможный подход: сперва конструирование новых членов, затем предков, затем выполнение тела.
Вызов перекрытой функции может быть опасен, если наследование множественное (в этом случае один из предков все еще содержит мусор, в то время как другой в конструкторе вызвал виртуальную функцию потомка).
Также, если члену в конструктор передан указатель на this, то возможна та же проблема.

Третий подход: считать конструктор методом настройки. Все члены (включая члены предков) изначально инициализированы по умолчанию (*), затем идет последовательная настройка в некотором детерминированном порядке (**).
(*) В большинстве случаев это обнуление, однако для указателей на VMT и виртуальные базы — это ненулевые значения.
(**) Так, можно сперва настроить часть своих членов (включая унаследованные), затем вызвать настройку предка, а затем продолжить.
На С++ это легко эмулируется: у всех классов в иерархии есть только конструктор без параметров, а также набор методов init с разными сигнатурами, и make-функция, чтобы можно было писать в одно выражение:
MyClass* p = MyDescendant::make(x,y,z);
// вместо
MyDescendant* d = new MyDescendant;
d->init(x,y,z);
MyClass* p = d;

Такого подхода придерживается Object Pascal (TP6 ... Delphi).
Перекуём баги на фичи!
Re[3]: Полиморфные вызовы в конструкторе
От: Dr_Sh0ck Беларусь  
Дата: 10.11.03 11:54
Оценка:
Здравствуйте, _wqwa, Вы писали:

_>"Существует" означает не только факт выделения памяти под объект, но еще и корректное его состояние.

_>Если не дожидаться отработки конструктора, состояние объекта может быть некорректным по логике работы системы.

Так было в С++. А С++ и С# это разные вещи. В С++ Страуструп придавал конструкторам С++ особое философское значение
Do not fake yourself ;)
ICQ#: 198114726
Re[3]: Полиморфные вызовы в конструкторе
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 10.11.03 11:55
Оценка:
Здравствуйте, WolfHound, Вы писали:

WH>Здравствуйте, mrhru, Вы писали:


M>>Такое поведение сделано и в Delphi. Проблем не вызывает.

WH>В дельфе нет конструкторов там есть фабричные функции что далеко не одно и тоже.

Интересно а что из себя представляет сишный конструктор???
На самом деле Дельфийный конструктор представляет из себя статические функции объекта
TObject = class
constructor Create;
procedure Free;
class function InitInstance(Instance: Pointer): TObject;
class function InstanceSize: Longint;
class function NewInstance: TObject; virtual;

, а так как они происходят от базового то и вызов их не составляет труда и глюков. Путем переопределения

procedure AfterConstruction; virtual;
procedure BeforeDestruction; virtual;
procedure FreeInstance; virtual;
destructor Destroy; virtual;

То есть все в соответствии с ООП в отличии от С++. То же самое и в Net/
M>>На самом деле, в момент вызова тела конструктора — объект уже "существует" (и даже инициализирован нулями).
WH>И чем нули отличаются от мусора? Очень часто надо инициализировать значениями отличными от нуля.

WH>ЗЫ Ой чую сейчас флейм начнется... А не переехоть ли нам от сюда?

Ты прав.
и солнце б утром не вставало, когда бы не было меня
Re: Полиморфные вызовы в конструкторе
От: Dr_Sh0ck Беларусь  
Дата: 10.11.03 11:58
Оценка: +1 -1
Здравствуйте, Alexey Shirshov, Вы писали:

AS>Всем привет!


AS>В С++, как известно, виртуальность вирутальных функций при вызове их в конструкторе "не работает". Функция вызывается "напрямую", в обход виртуальной таблице.

AS>В дотнете ситуация противоположенная: полиморфизм работает везде и даже в конструкторе. К чему это может привести? К тому, что некоторый код класса может быть вызван до того, как отработает конструктор данного экземпляра.

ИМХО в С# конструктору придается несколько другое значение на (уровне идеологии). В С# объект до выполнения конструктора физически находится в определенном состоянии.

AS>Прошу вас высказывать, не стесняться, с вои мысли по поводу того, насколько было верно решение проектировщиков дотнета, когда они отошли от принятого в С++ подхода.


Время покажет.

AS>Так же интересует, каковы плюсы полиморфизма всегда и везде и минусы решения С++. Возникали ли когда-нибудь у вас на этой почве проблемы?


В С++ виртуальные функции из конструктора не вызывал и в C# не собираюсь
Do not fake yourself ;)
ICQ#: 198114726
Re[4]: Полиморфные вызовы в конструкторе
От: Alexey Shirshov Россия http://wise-orm.com
Дата: 10.11.03 12:07
Оценка:
Здравствуйте, Dr_Sh0ck, Вы писали:

D_S>Здравствуйте, _wqwa, Вы писали:


_>>"Существует" означает не только факт выделения памяти под объект, но еще и корректное его состояние.

_>>Если не дожидаться отработки конструктора, состояние объекта может быть некорректным по логике работы системы.

D_S>Так было в С++. А С++ и С# это разные вещи. В С++ Страуструп придавал конструкторам С++ особое философское значение


В C++ так не было. А в шарпе (как и vb.net) это именно так.
... << RSDN@Home 1.1.0 stable >>
Re[4]: Полиморфные вызовы в конструкторе
От: Alexey Shirshov Россия http://wise-orm.com
Дата: 10.11.03 12:07
Оценка:
Здравствуйте, mrhru, Вы писали:

хъ

M>Так ведь нельзя получить ссылку на объект, не вызвав конструктора, т.е. для коде "извне" объекта — конструктор обязательно будет вызван до любого использования. А для кода "внутри" — память уже будет выделена и проинициализирована.


Я не понял о чем ты. Возможно потому, что ты меня не понял.
Вот код, который демонстрирует проблемы (vb.net):
Module Module1

    Class base
        Public Sub New()
            Console.WriteLine("base.ctor")
            foo() ' полиморфный вызов
        End Sub
        Public Overridable Sub foo()
            Console.WriteLine("base.foo")
        End Sub
    End Class

    Class derived : Inherits base
        Public Sub New()
            Console.WriteLine("derived.ctor")
        End Sub
        Public Overrides Sub foo()
            Console.WriteLine("derived.foo")
        End Sub
    End Class

    Sub Main()
        Console.WriteLine("Create base class instance")
        Dim c As New base
        Console.WriteLine("Create derived class instance")
        Dim c1 As New derived
    End Sub
End Module


Вывод:
Create base class instance
base.ctor
base.foo
Create derived class instance
base.ctor
derived.foo
derived.ctor
... << RSDN@Home 1.1.0 stable >>
Re[5]: Полиморфные вызовы в конструкторе
От: Dr_Sh0ck Беларусь  
Дата: 10.11.03 12:10
Оценка:
Здравствуйте, Alexey Shirshov, Вы писали:

D_S>>Так было в С++. А С++ и С# это разные вещи. В С++ Страуструп придавал конструкторам С++ особое философское значение


AS>В C++ так не было. А в шарпе (как и vb.net) это именно так.


Тьфу ты! Думал про одно, а написал так, как будто про другое...
Под "так было в С++" я имел ввиду статический вызов виртуальных методов из конструктора.
Do not fake yourself ;)
ICQ#: 198114726
Re[4]: Полиморфные вызовы в конструкторе
От: WolfHound  
Дата: 10.11.03 12:32
Оценка: :)
Здравствуйте, Serginio1, Вы писали:

S> То есть все в соответствии с ООП в отличии от С++. То же самое и в Net/

В С++ все в соответвсвии с надежностью. Но если очень хочется можно сделать в соответствии с ООП

struct some
{
  static some* create()
  {дельфевый конструктор
  }
  void destroy()
  {
    destroy_();
    delete this;
  }
  void destroy_()
  {дельфевый деструктор
  }
}

Болие подробно я писал в соседнем флейме.
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[2]: Полиморфные вызовы в конструкторе
От: bkat  
Дата: 10.11.03 19:31
Оценка:
Здравствуйте, mrhru, Вы писали:

M>Здравствуйте, Alexey Shirshov, Вы писали:


AS>>В С++, как известно, виртуальность вирутальных функций при вызове их в конструкторе "не работает". Функция вызывается "напрямую", в обход виртуальной таблице.

AS>>В дотнете ситуация противоположенная: полиморфизм работает везде и даже в конструкторе. К чему это может привести? К тому, что некоторый код класса может быть вызван до того, как отработает конструктор данного экземпляра.

AS>>Прошу вас высказывать, не стесняться, с вои мысли по поводу того, насколько было верно решение проектировщиков дотнета, когда они отошли от принятого в С++ подхода.


AS>>Так же интересует, каковы плюсы полиморфизма всегда и везде и минусы решения С++. Возникали ли когда-нибудь у вас на этой почве проблемы?


M>Такое поведение сделано и в Delphi. Проблем не вызывает.

И в Билдере тоже
У меня это вилилось в пару бессоных ночей...
Re[5]: Полиморфные вызовы в конструкторе
От: mrhru Россия  
Дата: 11.11.03 02:41
Оценка:
Здравствуйте, Alexey Shirshov, Вы писали:

M>>Так ведь нельзя получить ссылку на объект, не вызвав конструктора, т.е. для коде "извне" объекта — конструктор обязательно будет вызван до любого использования. А для кода "внутри" — память уже будет выделена и проинициализирована.


AS>Я не понял о чем ты. Возможно потому, что ты меня не понял.

AS>Вот код, который демонстрирует проблемы (vb.net):
...

AS>Вывод:

AS>Create base class instance
AS>base.ctor
AS>base.foo
AS>Create derived class instance
AS>base.ctor
AS>derived.foo
AS>derived.ctor

Т.е. конструктор предка вызывает виртуальную функцию, которая переопределена в потомке, но для неё поля потомка ещё не проинициализированы. Так?
Я ниже писал, что подобная проблема решена в Delphi — вызов конструктора предка из конструктора потомка осуществляется "вручную". (Естественно, как недостаток этого способа — можно забыть это сделать )
Но в любом случае, перед исполнением любого кода поля объектов будут инициализированы физически (как минимум обнулены).
Re: Полиморфные вызовы в конструкторе
От: Шахтер Интернет  
Дата: 11.11.03 06:24
Оценка: +2
Здравствуйте, Alexey Shirshov, Вы писали:

AS>Всем привет!


AS>В С++, как известно, виртуальность вирутальных функций при вызове их в конструкторе "не работает". Функция вызывается "напрямую", в обход виртуальной таблице.

AS>В дотнете ситуация противоположенная: полиморфизм работает везде и даже в конструкторе. К чему это может привести? К тому, что некоторый код класса может быть вызван до того, как отработает конструктор данного экземпляра.

AS>Прошу вас высказывать, не стесняться, с вои мысли по поводу того, насколько было верно решение проектировщиков дотнета, когда они отошли от принятого в С++ подхода.


AS>Так же интересует, каковы плюсы полиморфизма всегда и везде и минусы решения С++. Возникали ли когда-нибудь у вас на этой почве проблемы?


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

Решение в C++ вполне логичное -- нельзя использовать несконструированный обьект.

Проблемы здесь возникают. Например, в обьекте есть виртуальный метод close(). Хотелось бы его вызывать в деструкторе базы, ан нет, приходится дублировать код по очистке.

class A
 {
  public:
    
   virtual void close() { <очистка A> }
     ~A() { <очистка A> }
 };

class B : public A
 {
  public:
    
   virtual void close() { <очистка B> A::close(); }
     ~B() { <очистка B> }
 };


В C# же обьекты пре-инициализируются, так что проблема безопасности здесь решается. Появляются даже дополнительные возможности. Узнать финальный обьект, например. Так что опять же всё логично.
... << RSDN@Home 1.1 beta 2 >>
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Re: Полиморфные вызовы в конструкторе
От: Евгений Коробко  
Дата: 13.11.03 05:43
Оценка:
С таким вопросом сталкивался. При конструировании объектов часто оказывается, что из конструктора нужно вызвать виртуальную функцию. Например
class wnd
{
public:
wnd(){...CreateClass();};
protected:
virtual void CreateClass()=0;
};
...
Наиболее аккуратное решение таково:
class A
{
public:
A(){Init();};
protected:
virtual void Init();
class NOTUSED{};
A(A_NOTUSED *){};
}

class B:public A
{
public:
B():A((A::NOTUSED*)0){Init();};
protected:
B(A::NOTUSED*){};
void Init();
};

Громоздко, но что поделаешь...

Тут вот в продолжение темы такая идея. ООП выделяет следующие связи между классами: наследование, использование, агрерация, композиция (последние два иногда объединяются), инстанцирование. Эти концепции реализуются на языке С++ тем или иным образом:
наследование — механизмом наследования классов, инстанцирование — механизмом шаблонов,
использованием:
класс А использует класс В — в классе А есть поле типа B*.
агрегация:
класс А агругирует класс В — в классе А есть поле типа B* или B.
Композиция:
класс А включает класс В — в классе А есть поле типа B.
Всё вроде пучком. Но есть ещё наследование ассоциаций. Поясно на примере.
класс "машина" агрегирует класс "двигатель". "Спортивная машина" — потомок "машины", "турбодизельный двигатель" — потомок "двигателя". Я хочу сделать ассоциацию "спортивная машина агрегирует турбодизельный двигатель" потомком ассоциации "машина агрегирует двигатель". Но в языке С++ нет такого механизма! Поле класса не полиморфно.
Т.е.
class Car
{
...
Engine *m_Engine;
};
class SportCar
{
...
TDEngine *m_Engine; // <-- так нельзя!!!
};
Приходится эмулировать такое поведение с помощью перегружаемых виртуальных функций доступа!
...
virtual Engine* get_Engine();
1) В классе реально останется два поля
2) Из конструктора Car нельзя обратиться к get_Engine();
3) Как должна выглядеть set_Engine?

"Alexey Shirshov" <3658@news.rsdn.ru> wrote in message news:438026@news.rsdn.ru...
From: Alexey Shirshov rsdn alexey.europe.webmatrixhosting.net


Всем привет!

В С++, как известно, виртуальность вирутальных функций при вызове их в конструкторе "не работает". Функция вызывается "напрямую", в обход виртуальной таблице.
В дотнете ситуация противоположенная: полиморфизм работает везде и даже в конструкторе. К чему это может привести? К тому, что некоторый код класса может быть вызван до того, как отработает конструктор данного экземпляра.

Прошу вас высказывать, не стесняться, с вои мысли по поводу того, насколько было верно решение проектировщиков дотнета, когда они отошли от принятого в С++ подхода.

Так же интересует, каковы плюсы полиморфизма всегда и везде и минусы решения С++. Возникали ли когда-нибудь у вас на этой почве проблемы?
... << RSDN@Home 1.1.0 stable >>
Полиморфные вызовы в конструкторе Оценить
Posted via RSDN NNTP Server 1.8 beta
Евгений Коробко
Re[2]: Полиморфные вызовы в конструкторе
От: mrhru Россия  
Дата: 13.11.03 06:25
Оценка: +1
Здравствуйте, Евгений Коробко, Вы писали:

...

ЕК>Всё вроде пучком. Но есть ещё наследование ассоциаций. Поясно на примере.

ЕК>класс "машина" агрегирует класс "двигатель". "Спортивная машина" — потомок "машины", "турбодизельный двигатель" — потомок "двигателя". Я хочу сделать ассоциацию "спортивная машина агрегирует турбодизельный двигатель" потомком ассоциации "машина агрегирует двигатель". Но в языке С++ нет такого механизма! Поле класса не полиморфно.
ЕК>Т.е.
ЕК>class Car
ЕК>{
ЕК> ...
ЕК> Engine *m_Engine;
ЕК>};
ЕК>class SportCar
ЕК>{
ЕК>...
ЕК> TDEngine *m_Engine; // <-- так нельзя!!!
ЕК>};

class SportCar
{
...
//TDEngine *m_Engine; // <-- так нельзя!!!
//Engine *m_Engine; // <-- а так можно!!!
};
Тем более, что это уже определено в базовом классе, поэтому и не требует переопределения.

ЕК>Приходится эмулировать такое поведение с помощью перегружаемых виртуальных функций доступа!

ЕК> ...
ЕК> virtual Engine* get_Engine();
ЕК>1) В классе реально останется два поля

одно:
Engine *m_Engine;

ЕК>2) Из конструктора Car нельзя обратиться к get_Engine();

ЕК>3) Как должна выглядеть set_Engine?

Методы доступа д.б. представлять доступ к членам, а не к их созданию.
В данном случае, либо поле д.б. базового класса (Engine), либо интерфейсом (IEngine).
В любом случае, создаётся оно в конструкторе.
Re[4]: Полиморфные вызовы в конструкторе
От: _wqwa США  
Дата: 13.11.03 08:31
Оценка: +1
Здравствуйте, mrhru, Вы писали:

M>Здравствуйте, _wqwa, Вы писали:


M>>>Такое поведение сделано и в Delphi. Проблем не вызывает.

M>>>На самом деле, в момент вызова тела конструктора — объект уже "существует" (и даже инициализирован нулями).


M>В данном случае, имхо, "существует" — это означает, что память выделена, (1) физически инициализирована. т.е. гарантировано отсутствие мусора типа ссылок в "никуда", и (2)работоспособен механизм наследования (раз объект уже существует)


(2) Не обязательно следует из (1). Все-таки для (2) требуется логическая инициализация


_>>Если не дожидаться отработки конструктора, состояние объекта может быть некорректным по логике работы системы.


M>По моему очень скромному имхо, в Delphi это сделано даже чуть лучше чем в С# — там возможно вызывать конструктор базового класса в любом месте конструктора текущего (а можно и забыть вызвать ). В этом смысле — для программиста — конструктор становится просто чем-то вроде функции Init. Это, как минимум удобно.

Не знаю, как там в C# ... А что мешает в случае надобности сделать так же на любом другом языке? Не пиши конструктора, пиши метод Init() и вызывай его, когда нравится.
Кто здесь?!
Re[3]: Полиморфные вызовы в конструкторе
От: Евгений Коробко  
Дата: 15.11.03 12:09
Оценка:
>class SportCar
>{
>...
>//TDEngine *m_Engine; // <-- так нельзя!!!
>//Engine *m_Engine; // <-- а так можно!!!
>};
Так в том-то и дело! В этом случае SportCar не может пользоваться спецификой класса TDEngine. Наследования ассоциации аггрегации не происходит.
Допустим, использовать классом Car класса Engine заключалось в вызове метода Force(int); Ассоциация аггрегирования между SportCar и TDEngine является потомком этой ассоциации и, помимо унаследованной функции Force содержит фунцию EnableTurbo(bool). Спрашивается, как я могу получить доступ к методу EnableTurbo из методов SportCar? Очевидно, никак.
Что тут можно сделать?
1) В SportCar создать новое поле типа TDEngine. Это плохое решение, т.к. методы, унаследованные от Car, будут обращаться к старому полю. Это недопустимо.
2) Сделать так, чтобы поле m_Engine, имея тип Engine*, в классе SportCar реально содержало указатель на TDEngine, и в методах SportCar использовать операции приведения типа static_cast или dynamic_cast. Некрасиво, но жить можно было бы, если бы не одно обстоятельство: объект-то создаётся в конструкторе класса Car. Уничтожать его в конструкторе SportCar и создавать вместо него TDEngine — не вариант, сами понимаете. А сделать функцию создания Engine полиморфной нельзя — из конструктора виртуальные функции не вызываются.
Что же остаётся? Описанная в предыдущем посте техника через пустой защищённый конструктор. Ну, можно ещё сделать извращение через фабрику классов:

typedef Engine* (*EngineFactory)(); // точно сходу не помню синтаксиса указателей на функции
class Car
{
public:
Car(EngineFactory factory=CarEngineFactory);
private:
static Engine* CarEngineFactory();
};

class SportCar
{
public:
SportCar(EngineFactory factory=SportCarEngineFactory):Car(factory);
private:
static Engine* SportCarEngineFactory();
};
Если же таких полей несколько, то очень быстро мы поймём, что то, что мы делаем — это создание альтернативной vtbl, которая передаётся параметром в конструктор.
Posted via RSDN NNTP Server 1.8 beta
Евгений Коробко
Re[4]: Полиморфные вызовы в конструкторе
От: mrhru Россия  
Дата: 17.11.03 04:24
Оценка:
Здравствуйте, Евгений Коробко, Вы писали:

>>class SportCar

>>{
>>...
>>//TDEngine *m_Engine; // <-- так нельзя!!!
>>//Engine *m_Engine; // <-- а так можно!!!
>>};
ЕК>Так в том-то и дело! В этом случае SportCar не может пользоваться спецификой класса TDEngine. Наследования ассоциации аггрегации не происходит.
ЕК>Допустим, использовать классом Car класса Engine заключалось в вызове метода Force(int); Ассоциация аггрегирования между SportCar и TDEngine является потомком этой ассоциации и, помимо унаследованной функции Force содержит фунцию EnableTurbo(bool). Спрашивается, как я могу получить доступ к методу EnableTurbo из методов SportCar? Очевидно, никак.
ЕК>Что тут можно сделать?
ЕК>1) В SportCar создать новое поле типа TDEngine. Это плохое решение, т.к. методы, унаследованные от Car, будут обращаться к старому полю. Это недопустимо.
ЕК>2) Сделать так, чтобы поле m_Engine, имея тип Engine*, в классе SportCar реально содержало указатель на TDEngine, и в методах SportCar использовать операции приведения типа static_cast или dynamic_cast. Некрасиво, но жить можно было бы, если бы не одно обстоятельство: объект-то создаётся в конструкторе класса Car. Уничтожать его в конструкторе SportCar и создавать вместо него TDEngine — не вариант, сами понимаете. А сделать функцию создания Engine полиморфной нельзя — из конструктора виртуальные функции не вызываются.

Есть ещё вариант:
После конструирования, явно вызывать виртуальную Init().
Ну, правда, делать это можно и в фабрике. Точнее говоря, только в фабрике.
В результате, сопровождая каждый класс ***Car классом-фабрикой с единственным экземпляром-singleton и виртуальным CreateCar(), приходим (как упоминал ранее (в другой ветке) Кодт) к эмуляции классов как объектов с виртуальными конструкторами a la Delphi.

А для доступа в SportCar к полю Engine* как к TDEngine используем static_cast (имхо dynamic_cast хуже, поскольку нам нужно приведение в compile-, а не run-time) в невиртуальном методе доступа.
Re[5]: Полиморфные вызовы в конструкторе
От: Евгений Коробко  
Дата: 21.11.03 06:06
Оценка:
Слава богу, хоть кто-то согласился, что проблема есть! А то все говорят, мол, если такое возникает, значит, что-то неправильно спроектировано.

P.S. Я уже не говорюпро ситуацию, когда хотелось бы сделать композицию:
class Car
{
...
Engine m_Engine; // статическое выделение памяти
};
Эта ситуация вообще неустранима.
Posted via RSDN NNTP Server 1.8 beta
Евгений Коробко
Re[3]: Полиморфные вызовы в конструкторе
От: VladD2 Российская Империя www.nemerle.org
Дата: 06.12.03 08:55
Оценка:
Здравствуйте, Alexey Shirshov, Вы писали:

S>А если для правильной работы функции нужна будет инициализация в конструкторе (который еще не вызван)?


Ну, тогда начинать зудумаваться о том зачем нужно проектирование.
... << RSDN@Home 1.1.2 beta 1 >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.