Здравствуйте, Евгений Коробко, Вы писали:
...
ЕК>Всё вроде пучком. Но есть ещё наследование ассоциаций. Поясно на примере.
ЕК>класс "машина" агрегирует класс "двигатель". "Спортивная машина" — потомок "машины", "турбодизельный двигатель" — потомок "двигателя". Я хочу сделать ассоциацию "спортивная машина агрегирует турбодизельный двигатель" потомком ассоциации "машина агрегирует двигатель". Но в языке С++ нет такого механизма! Поле класса не полиморфно.
ЕК>Т.е.
ЕК>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).
В любом случае, создаётся оно в конструкторе.
Здравствуйте, mrhru, Вы писали:
M>Здравствуйте, _wqwa, Вы писали:
M>>>Такое поведение сделано и в Delphi. Проблем не вызывает.
M>>>На самом деле, в момент вызова тела конструктора — объект уже "существует" (и даже инициализирован нулями).
M>В данном случае, имхо, "существует" — это означает, что память выделена, (1) физически инициализирована. т.е. гарантировано отсутствие мусора типа ссылок в "никуда", и (2)работоспособен механизм наследования (раз объект уже существует)
(2) Не обязательно следует из (1). Все-таки для (2) требуется
логическая инициализация
_>>Если не дожидаться отработки конструктора, состояние объекта может быть некорректным по логике работы системы.
M>По моему очень скромному имхо, в Delphi это сделано даже чуть лучше чем в С# — там возможно вызывать конструктор базового класса в любом месте конструктора текущего (а можно и забыть вызвать ). В этом смысле — для программиста — конструктор становится просто чем-то вроде функции Init. Это, как минимум удобно.
Не знаю, как там в C#
... А что мешает в случае надобности сделать так же на любом другом языке? Не пиши конструктора, пиши метод Init() и вызывай его, когда нравится.
>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
Здравствуйте, Евгений Коробко, Вы писали:
>>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) в невиртуальном
методе доступа.
Слава богу, хоть кто-то согласился, что проблема есть! А то все говорят, мол, если такое возникает, значит, что-то неправильно спроектировано.
P.S. Я уже не говорюпро ситуацию, когда хотелось бы сделать композицию:
class Car
{
...
Engine m_Engine; // статическое выделение памяти
};
Эта ситуация вообще неустранима.
Posted via RSDN NNTP Server 1.8 beta
Здравствуйте, Alexey Shirshov, Вы писали:
S>А если для правильной работы функции нужна будет инициализация в конструкторе (который еще не вызван)?
Ну, тогда начинать зудумаваться о том зачем нужно проектирование.
... << RSDN@Home 1.1.2 beta 1 >>