87.118.102.154 ситуэйшн такая: Код (Text): class Parent { public: int SomeMethod(); // некоторый метод }; class Child : public Parent { public: int ChildMethod(); // метод в дочернем классе. в родительском такого нет. }; Parent * foo(); // ф-ция (или метод какого-либо класса - неважно) // вот оч. интересует законность нижеследующего кода int main() { Child * pChildObject = foo(); pChildObject->ChildMethod(); // будет ли работать // а) если foo() вернет объект класса Child? // (думается, что будет) // б) а что будет если foo() вернет объект класса Parent? } Спасибо.
Child * pChildObject = foo(); - компилятор такое не съест, потому что указатели имеют несовместивый тип. Вернее совместимый, но в другую сторону.
Работать будет если: a) ChildMethod не виртуальный. b) ChildMethod не обращается к данным которых нет в Parent. Ну а заставить компилятор съесть можно всё что угодно. Child * pChildObject = (Child*)foo();
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() взвращает базовый?
Всё нормально, дерзай далее. Только общий инерфейс должен быть и у базового и у всех остальных, сделай этот метод в базовом виртуальным и спи спокойно. Да и GetSprite() не должен возвращать базовый.
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<..> и иже с ним?
Я о том что создаваться-то должны нормальные, производные классы, а возвращать можно и указатель на базовый. Указатель-то это просто адрес. Нормально, но в последнем случае тебе придётся как-то определить что это за класс, RTTI тут может немного помочь. Если только моветон. static_cast<> и () абсолютно равноценны, но считается что приведение это потенциально опасная операция, и что static_cast сильнее бросается в глаза, а значит лучше. Но мне порой просто лень писать такую длинную конструкцию.
Green_DiCk Может тогда в движке стоит сделать несколько коллекций и методы GetSprite, GetSuperSprite, GetSuperPuperSprite и соответственно разные методы для добавления спрайтов?! Причём можно сделать чтобы GetSprite искал во всех списках, GetSuperSprite искал только в списках содержащих SuperSprite и списках производных от него и так далее. На мой взгляд вызвать метод с другим именем это лучше чем явное приведение типов.
Black_mirror +1 Green_DiCk в любом случае, обращение к RTTI и/или проверкам через dynamic_cast свидетельствуют о кривизне архитектурных решений. про то, что всё это плохо сказывается на перфомансе я уже молчу. ps: и не забудь добавить какой-либо виртуальный метод в базовый класс спрайта (а еще лучше не забыть об объявлении деструктора базового класса виртуальным).
добейся того, чтобы обращения к этим инстанциям НЕ БЫЛО. говори hide() только тем инстанциям, которые умеют это делать. как этого достичь -- выше уважаемый Black_mirror привел один способ решения. хотя не факт, что это единственный способ. думай я так понимаю, hide/unhide должен делать какой-то менеджер, тем более что это 2D. скажем, если какому либо спрайту говорится hide(), то он автоматом в цикле обходит все свои спрайты, лежащий НАД ним/НА нём и говорит также им hide(). или же, как вариант, можно делать hide() только каким-то спрайтам первого слоя, и вести актуальный список (контейнер) активных спрайтов. при рендеринге пробегаться сначала по всем этим активным спрайтам первого слоя, и говорить им render(). далее, выводить спрайты второго слоя (анимация, персонажи, блоки, итд). короче, побольше конкретики распиши.
varnie Black_mirror Делать для каждого класса свой фабричный метод, чтобы с типами было красиво? RTTI и dynamic_cast для приведения не нужны. И их применение совсем не свидетельство кривизны. Вот и молчи коли не знаешь о чём говоришь.
varnie Ну так надо во всём знать меру. Например в дотнете постоянно идёт много проверок в рантайме и ничего как-то живут. Потом в данном случае это и не нужно. Возьмите ком, почему-то там не вводили множество методов - фабрик, и ничего работает себе. Вобщем есть разные случаи, и возможны разные варианты. Например если потребуется ввести систему плагинов для спрайтов, то один универсальный, фабричный метод будет очень кстати. А делать dynamic_cast или нет, это уже вопрос клиентской архитектуры, он то знает, что за тип он запрашивает.
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); } ?
Green_DiCk у потомков в конструкторе вызывай конструктор их базового класса, передавая ему нужные параметры. Код (Text): BaseClass (HWND hWnd, const char * sprite_name) :_hwnd(hwnd), _sprite_name(sprite_name) { } ChildClass (HWND hWnd, const char * sprite_name) :BaseClass(hwnd, sprite_class) { } а в базовом классе можешь вынести конструктор в protected-секцию, тем самым показав, что прямые инстанции базового класса создавать нельзя, а можно лишь создавать его потомков, как в коде моем выше.
217.118.66.101 А что по этому поводу посоветуете, камрады?: Код (Text): class Parent { int a; }; class Child : public Parent { int b; }; // очевидно sizeof(Parent) == 4, а sizeof(Сhild) == 8, так? // тогда как будет работать следующее?: void main() { std::map<int, Parent> my_map; Child child_object; my_map.insert((Parent)child_object); // здесь не совсем корректно, но смысл ясен } Что произойдет в этом случае? Словарь у нас на элементы типа Parent, а мы пихаем в него нечто большее. И как вообще такие вещи реализуются? Т.е. нужен массив состоящий из экземпляров разных классов (не совсем разных, а унаследованных друг от друга). Как тогда определять тип массива если заранее не известно что конкретно в нем будет храниться?
217.118.66.101 Booster >На это есть указатели или ссылки. На что "на это"? Меня интересует что предпринимает STL когда в контейнер вставляют экземпляр класса, большего по размеру, чем тот на который рассчитан контейнер. STL скорее всего ничего особенного не предпринимает, а просто расценивает вставляемый экземпляр как экземпяр того класса, под который рассчитан контейнер. Но что происходит в этом случае? Отсекаются поля дочернего класса? Или что?