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...
Пока на собственное сообщение не было ответов, его можно удалить.