Подскажите по наследованию...

Тема в разделе "LANGS.C", создана пользователем Green_DiCk, 4 дек 2008.

  1. Green_DiCk

    Green_DiCk New Member

    Публикаций:
    0
    Регистрация:
    8 июл 2007
    Сообщения:
    338
    87.118.102.154

    ситуэйшн такая:

    Код (Text):
    1. class Parent
    2. {
    3.      public:
    4.      int SomeMethod();   // некоторый метод
    5. };
    6.  
    7.  
    8. class Child : public Parent
    9. {
    10.        public:
    11.        int ChildMethod();  // метод в дочернем классе. в родительском такого нет.
    12. };
    13.  
    14.  
    15. Parent * foo();           // ф-ция (или метод какого-либо класса - неважно)
    16.  
    17. // вот оч. интересует законность нижеследующего кода
    18. int main()
    19. {
    20.      Child * pChildObject = foo();
    21.      pChildObject->ChildMethod();   // будет ли работать
    22.                                     // а) если foo() вернет объект класса Child?
    23.                                     //   (думается, что будет)
    24.                                     // б) а что будет если foo() вернет объект класса Parent?
    25. }
    Спасибо.
     
  2. Black_mirror

    Black_mirror Active Member

    Публикаций:
    0
    Регистрация:
    14 окт 2002
    Сообщения:
    1.035
    Child * pChildObject = foo(); - компилятор такое не съест, потому что указатели имеют несовместивый тип. Вернее совместимый, но в другую сторону.
     
  3. Booster

    Booster New Member

    Публикаций:
    0
    Регистрация:
    26 ноя 2004
    Сообщения:
    4.860
    Работать будет если: a) ChildMethod не виртуальный. b) ChildMethod не обращается к данным которых нет в Parent.
    Ну а заставить компилятор съесть можно всё что угодно.
    Child * pChildObject = (Child*)foo();
     
  4. Green_DiCk

    Green_DiCk New Member

    Публикаций:
    0
    Регистрация:
    8 июл 2007
    Сообщения:
    338
    87.118.102.154

    а тогда что съест? какой-нить _cast<Child *> если замутить? static, dynamic или еще чего?
    Вообщем изложу лучше суть проблемы. Пишу 2D-движок. Есть класс "Движок". В нем содержится массив "Спрайтов".

    ну если упрощенно, то:

    class Engine
    {
    Sprite sprites[20]; // Sprite - базовый класс. еще есть
    // class SuperSprite : public Sprite {};
    // class SuperPuperSprite : public SuperSprite {};
    // ну и т.д.
    };

    Спрайты у меня нескольких типов - есть попроще, есть поумнее. Т.е. есть определенная иерархия спрайтов. вот нужно мне сказать "Движку" чтобы он например спрятал тот или иной спрайт.
    Здесь как мне кажется уместна такая конструкция:

    Engine.GetSprite(name)->Hide(); // Метод GetSprite() возвращает Sprite *


    Но спрайты то могут быть разные. Что если один из спрайтов не умеет прятаться? Т.е. метод Hide() есть только у дочерних спрайтов, а GetSprite() взвращает базовый?
     
  5. Booster

    Booster New Member

    Публикаций:
    0
    Регистрация:
    26 ноя 2004
    Сообщения:
    4.860
    Всё нормально, дерзай далее. Только общий инерфейс должен быть и у базового и у всех остальных, сделай этот метод в базовом виртуальным и спи спокойно. Да и GetSprite() не должен возвращать базовый.
     
  6. Green_DiCk

    Green_DiCk New Member

    Публикаций:
    0
    Регистрация:
    8 июл 2007
    Сообщения:
    338
    87.118.102.154

    >Да и GetSprite() не должен возвращать базовый.

    А что он тогда должен возвращать?

    Я планировал вот как это все использовать:

    Sprite * spr1 = Engine.GetSprite(spr1_name);
    SuperSprite * spr2 = (SuperSprite *)Engine.GetSprite(spr2_name);
    SuperPuperSprite * spr3 = (SuperSprite *)Engine.GetSprite(spr3_name);

    spr1->CommonMethod(); // общий для всех классов метод
    spr2->CommonMethod(); // .. так, или же...
    spr2->SuperSpriteMethod(); // метод, специфичный для кл. SuperSprite

    т.к. я знаю какой из спрайтов у меня какой, то я могу определить какие методы у каждого спрайта можно вызывать а какие нет.
    т.е. я не буду писать spr1->SuperSpriteMethod();


    >Ну а заставить компилятор съесть можно всё что угодно.
    >Child * pChildObject = (Child*)foo();

    а вообще си-подобный кастинг разве не моветон? для чего все эти static_cast<..> и иже с ним?
     
  7. Booster

    Booster New Member

    Публикаций:
    0
    Регистрация:
    26 ноя 2004
    Сообщения:
    4.860
    Я о том что создаваться-то должны нормальные, производные классы, а возвращать можно и указатель на базовый. Указатель-то это просто адрес.

    Нормально, но в последнем случае тебе придётся как-то определить что это за класс, RTTI тут может немного помочь.

    Если только моветон. static_cast<> и () абсолютно равноценны, но считается что приведение это потенциально опасная операция, и что static_cast сильнее бросается в глаза, а значит лучше. Но мне порой просто лень писать такую длинную конструкцию.
     
  8. Green_DiCk

    Green_DiCk New Member

    Публикаций:
    0
    Регистрация:
    8 июл 2007
    Сообщения:
    338
    вроде более менее ясно. спасибо.
     
  9. Black_mirror

    Black_mirror Active Member

    Публикаций:
    0
    Регистрация:
    14 окт 2002
    Сообщения:
    1.035
    Green_DiCk
    Может тогда в движке стоит сделать несколько коллекций и методы GetSprite, GetSuperSprite, GetSuperPuperSprite и соответственно разные методы для добавления спрайтов?! Причём можно сделать чтобы GetSprite искал во всех списках, GetSuperSprite искал только в списках содержащих SuperSprite и списках производных от него и так далее. На мой взгляд вызвать метод с другим именем это лучше чем явное приведение типов.
     
  10. varnie

    varnie New Member

    Публикаций:
    0
    Регистрация:
    2 янв 2005
    Сообщения:
    1.785
    Black_mirror
    +1

    Green_DiCk
    в любом случае, обращение к RTTI и/или проверкам через dynamic_cast свидетельствуют о кривизне архитектурных решений. про то, что всё это плохо сказывается на перфомансе я уже молчу.

    ps: и не забудь добавить какой-либо виртуальный метод в базовый класс спрайта (а еще лучше не забыть об объявлении деструктора базового класса виртуальным).
     
  11. varnie

    varnie New Member

    Публикаций:
    0
    Регистрация:
    2 янв 2005
    Сообщения:
    1.785
    добейся того, чтобы обращения к этим инстанциям НЕ БЫЛО. говори hide() только тем инстанциям, которые умеют это делать.
    как этого достичь -- выше уважаемый Black_mirror привел один способ решения. хотя не факт, что это единственный способ. думай;)

    я так понимаю, hide/unhide должен делать какой-то менеджер, тем более что это 2D. скажем, если какому либо спрайту говорится hide(), то он автоматом в цикле обходит все свои спрайты, лежащий НАД ним/НА нём и говорит также им hide().

    или же, как вариант, можно делать hide() только каким-то спрайтам первого слоя, и вести актуальный список (контейнер) активных спрайтов. при рендеринге пробегаться сначала по всем этим активным спрайтам первого слоя, и говорить им render().
    далее, выводить спрайты второго слоя (анимация, персонажи, блоки, итд).

    короче, побольше конкретики распиши.
     
  12. Booster

    Booster New Member

    Публикаций:
    0
    Регистрация:
    26 ноя 2004
    Сообщения:
    4.860
    varnie
    Black_mirror
    Делать для каждого класса свой фабричный метод, чтобы с типами было красиво?
    RTTI и dynamic_cast для приведения не нужны. И их применение совсем не свидетельство кривизны.

    Вот и молчи коли не знаешь о чём говоришь.
     
  13. varnie

    varnie New Member

    Публикаций:
    0
    Регистрация:
    2 янв 2005
    Сообщения:
    1.785
    Booster
    а обосновать?

    (c) Bruce Eckel (Thinking In C++, Volume 1)
     
  14. Booster

    Booster New Member

    Публикаций:
    0
    Регистрация:
    26 ноя 2004
    Сообщения:
    4.860
    varnie
    Ну так надо во всём знать меру. Например в дотнете постоянно идёт много проверок в рантайме и ничего как-то живут. Потом в данном случае это и не нужно. Возьмите ком, почему-то там не вводили множество методов - фабрик, и ничего работает себе. Вобщем есть разные случаи, и возможны разные варианты. Например если потребуется ввести систему плагинов для спрайтов, то один универсальный, фабричный метод будет очень кстати. А делать dynamic_cast или нет, это уже вопрос клиентской архитектуры, он то знает, что за тип он запрашивает.
     
  15. Green_DiCk

    Green_DiCk New Member

    Публикаций:
    0
    Регистрация:
    8 июл 2007
    Сообщения:
    338
    87.118.102.154

    varnie
    >говори hide() только тем инстанциям, которые умеют это делать.
    Так я так и собираюсь поступать. Это же я сам руками добавляю спрайты. Они же там не генерятся автоматически рандомного типа. А раз я сам добавляю, то стало быть и знаю у какого спрайта какой метод можно вызывать. Вообще не могу представить себе правдоподобную ситуацию в которой я бы не знал с каким спрайтом имею дело. Про то, что будет если обратиться к несуществующему методу, я спрашивал просто из любопытства.
    Поэтому вариант Booster'а считаю самым приемлемым. Если только лагать будет что-нибудь, тогда сделаю как Black_mirror посоветовал. а так - слишком много методов. неэлегантно как-то. хотя согласен - безопаснее и все такое.


    Вы лучше вот что еще скажите - у меня в классе Sprite есть private HWND hWnd; - хэндл окна в котором спрайты рисуются. И есть такой конструктор:

    Sprite(HWND hWnd, const char * sprite_name) {this->hWnd = hWnd; ... }

    А как быть с контсрукторами дочерних классов? Если сделать так:

    SuperSprite (HWND hWnd, const char * sprite_name) {this->hWnd = hWnd; ... }

    то не скомпилируется, т.к. идет обращение к приватному члену родительского класса. Что надо сделать? Объявить hWnd как protected или что-то типа этого:

    SuperSprite (HWND hWnd, const char * sprite_name)
    {
    Sprite::Sprite(hWnd, sprite_name);
    }
    ?
     
  16. varnie

    varnie New Member

    Публикаций:
    0
    Регистрация:
    2 янв 2005
    Сообщения:
    1.785
    Green_DiCk
    у потомков в конструкторе вызывай конструктор их базового класса, передавая ему нужные параметры.
    Код (Text):
    1. BaseClass (HWND hWnd, const char * sprite_name) :_hwnd(hwnd), _sprite_name(sprite_name)
    2.   { }
    3. ChildClass (HWND hWnd, const char * sprite_name) :BaseClass(hwnd, sprite_class)
    4.   { }
    а в базовом классе можешь вынести конструктор в protected-секцию, тем самым показав, что прямые инстанции базового класса создавать нельзя, а можно лишь создавать его потомков, как в коде моем выше.
     
  17. Green_DiCk

    Green_DiCk New Member

    Публикаций:
    0
    Регистрация:
    8 июл 2007
    Сообщения:
    338
    varnie
    ок. я примерно так и предполагал. спс.
     
  18. Green_DiCk

    Green_DiCk New Member

    Публикаций:
    0
    Регистрация:
    8 июл 2007
    Сообщения:
    338
    217.118.66.101

    А что по этому поводу посоветуете, камрады?:

    Код (Text):
    1. class Parent
    2. {
    3.      int a;
    4. };
    5.  
    6. class Child : public Parent
    7. {
    8.     int b;
    9. };
    10.  
    11. // очевидно sizeof(Parent) == 4, а sizeof(Сhild) == 8, так?
    12. // тогда как будет работать следующее?:
    13.  
    14. void main()
    15. {
    16.     std::map<int, Parent> my_map;
    17.     Child child_object;
    18.     my_map.insert((Parent)child_object);     // здесь не совсем корректно, но смысл ясен
    19. }
    Что произойдет в этом случае? Словарь у нас на элементы типа Parent, а мы пихаем в него нечто большее. И как вообще такие вещи реализуются? Т.е. нужен массив состоящий из экземпляров разных классов (не совсем разных, а унаследованных друг от друга). Как тогда определять тип массива если заранее не известно что конкретно в нем будет храниться?
     
  19. Booster

    Booster New Member

    Публикаций:
    0
    Регистрация:
    26 ноя 2004
    Сообщения:
    4.860
    На это есть указатели или ссылки.
     
  20. Green_DiCk

    Green_DiCk New Member

    Публикаций:
    0
    Регистрация:
    8 июл 2007
    Сообщения:
    338
    217.118.66.101

    Booster
    >На это есть указатели или ссылки.

    На что "на это"? Меня интересует что предпринимает STL когда в контейнер вставляют экземпляр класса, большего по размеру, чем тот на который рассчитан контейнер.

    STL скорее всего ничего особенного не предпринимает, а просто расценивает вставляемый экземпляр как экземпяр того класса, под который рассчитан контейнер. Но что происходит в этом случае? Отсекаются поля дочернего класса? Или что?