Войти
ФлеймФорумПрограммирование

вопрос по наследованию в С++

Страницы: 1 2 3 Следующая »
#0
9:47, 10 мар. 2013

вобщем, вот такая проблема:

есть базовый абстрактный класс класс:

class GameObject
{
    int x;
    int y;
public:
virtual void Draw() = 0;
};

есть производный класс от базового:

class Ship : public GameObject
{
int pricelX;
int pricelY;

public:
Ship(int, int);
void Draw();
void SetPricelX(int);
void SetPricelY(int);
};

в производном классе добавлены 2 новых поля и 2 новых метода + конструктор.

вроде бы по правилам наследования поля x и y должны наследоваться из базового класса, но
когда определяем конструктор

Ship::Ship(int _x, int _y){
  x = _x;
  y = _y;
}
сразу ошибка: error C2248: GameObject::x: невозможно обратиться к private член, объявленному в классе "GameObject"
заработает только тогда, когда в Ship объявим x и y. Но тогда смысл наследования?


Далее:
когда делаю вот так в главной функции

GameObject* ship = new Ship(350, 350);

ship->SetPricelX(50);

Получаем ошибку
error C2039: SetPricelX: не является членом "GameObject"

и она исчезает только тогда, когда я объявляю SetPricelX виртуальным методом в классе GameObject. Получается, я не могу дополнить дочерний объект новыми методами и полями. ВСЕ Методы и поля ВСЕХ дочерних объектов должны быть в базовом классе. Или всё таки могу дополнить? объясните. Не могу разобраться, т.к. во всех книгах и видео этот вопрос хитро обходится. Везде все классы наследники имеют одинаковые поля, и используют только один виртуальный метод, который благополучно перегружается для всех. Мне нужна помощь именно с тем, как правильно дополнить дочерний класс какими то своими полями и методами, и чтобы при этом работало позднее связывание.


#1
10:06, 10 мар. 2013

Я не вижу поля x и y ни в одном из классов. Ты что-то пропустил?

#2
10:23, 10 мар. 2013

Atlant1Q
> GameObject* ship = new Ship(350, 350);
Ты создаешь объект Ship, вписанный в интерфейс GameObject, и физически не можешь использовать то, чего нет в этом интерфейсе.
Можно скастовать GameObject к Ship, если методы уникальны для Ship, или определить методы в интерфейсе, если они общие.

#3
11:27, 10 мар. 2013

Atlant1Q
> невозможно обратиться к private член, объявленному в классе "GameObject"

То что объявлено в private: секции доступно только из методов текущего класса и не будет доступно ни извне ни даже в наследниках. Если ты хочешь запретить доступ извне но разрешить доступ в наследниках ты должен использовать protected: секцию.

> Получаем ошибку
> error C2039: SetPricelX: не является членом "GameObject"

Тут важно понимать что когда ты указатель на объект (Ship *) присваиваешь указателю на его базовый класс (GameObject *), то действительно как бы теряешь связь с классом Ship и в принципе в этом и заключается цимес ООП-а - ты должен базовый класс спроектировать таким образом чтобы в нём были наиболее абстрактные методы позволяющие при их вызовах делегировать настоящую работу уже подлежащим производным классам так чтобы не задумываться в коде работающем со списком объектов с каким именно объектом (какого производного класса) идет работа - это и есть самый цимес.
Отнюдь - это не означает что ты все поля и данные производных классов должен запихать в базовый - о нет нет, это как раз неправильно. Ты просто должен в базовом классе иметь такой набор виртуальных методов чтобы покрыть нужные вещи на ура.
Тем не менее иногда возникает необходимость понять какой именно объект производного класса перед тобой находится и дернуть его метод отсутствующий в базовом классе - такое бывает, да.
Для этого во первых предусмотрен оператор dynamic_cast - это для случаев когда ты не уверен какой именно производный объект лежит в указателе на базовый класс и как бы "тестируешь" а не Ship * ли тут у нас? И если да - то делаешь дело, если нет - пропускаешь или проверяешь на другой объект.
В случае если ты 100%-но уверен какой именно объект лежит ты можешь использовать C-style cast (он побыстрее, но если ошибиться программа упадет):

GameObject* ship = new Ship(350, 350);

((Ship *)ship)->SetPricelX(50);


Тем не менее когда появляется необходимость делать эти так называемые "апкасты" (up cast), то скорее всего архитектура разработана неправильно.

Если немного подумать над твоей постановкой задачи то скорее всего дело в том что ты из кода который принимает сигналы от кнопок и мыши управляешь кораблем - так вот - этот код не нуждается в абстракциях базового класса и ты можешь просто завести специально для него указатель на Ship прямо как Ship *ship - и обращаться к кораблю именно через этот указатель. Тут всё логично - и сам корабль существует в единичном числе и код им управляющий не нуждается в лишних апкастах а просто пусть имеет непосредственно указатель на ship * и дергает ему методы.
В то же время для делегирования кораблю таких общих вещей как Tick, Draw, TakeDamage и т.д. - т.е. те вещи которые обсчитываются в общих, абстрактных циклах имеющих дело лишь со списком GameObject-ов и не знающих какие именно производные в этом списке находятся - так же надо в этот собственно список добавить "дублирующий" указатель на ship как GameObject * и всё.

#4
12:56, 10 мар. 2013

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

#5
13:09, 10 мар. 2013

MarkoPolo
> Не делай виртуального Draw, это медленно. Попытайся сделать так, чтобы все методы, которые ты будешь использовать часто не были виртуальными.

У него семестровая по информатике где основная задача - создать иерархию классов, так что всё ровно надо делать наоборот.

#6
13:15, 10 мар. 2013

MarkoPolo
> Не делай виртуального Draw, это медленно. Попытайся сделать так, чтобы все
> методы, которые ты будешь использовать часто не были виртуальными.
эпик

#7
13:36, 10 мар. 2013

Atlant1Q
Лучше возьми книжку и выучи язык.
MarkoPolo
> Не делай виртуального Draw, это медленно. Попытайся сделать так, чтобы все
> методы, которые ты будешь использовать часто не были виртуальными.
и ты тоже

#8
13:42, 10 мар. 2013

=A=L=X=
> Тем не менее когда появляется необходимость делать эти так называемые "апкасты"
> (up cast), то скорее всего архитектура разработана неправильно.
вообще то это downcast

#9
13:48, 10 мар. 2013

MarkoPolo
> Попытайся сделать так, чтобы все методы, которые ты будешь использовать часто не были виртуальными.
Есть идеи как?

#10
14:26, 10 мар. 2013

laMer007
Наследование для слабаков. Настоящие джедаи пишут монолиты.

#11
14:29, 10 мар. 2013

Mephi1984
> Я не вижу поля x и y ни в одном из классов. Ты что-то пропустил?

да, пропустил, исправил первый пост.

#12
14:37, 10 мар. 2013

MarkoPolo
> этот код не нуждается в абстракциях базового класса и ты можешь просто завести
> специально для него указатель на Ship прямо как Ship *ship - и обращаться к
> кораблю именно через этот указатель.

но я тогда не смогу сделать так, потому что у меня объекты находятся в списке с типом GameObject* :

typedef std::list <GameObject*> GameObject_list;
GameObject_list objects;

//...

GameObject *ship;

//...

Form1(void){
  InitializeComponent();
      
  ship = new Ship(350, 350);
  objects.push_back(ship);

}
//...

private: System::Void timer1_Tick(System::Object^  sender, System::EventArgs^  e) {
     for( GameObject_list::iterator i = objects.begin(); i != objects.end(); i++ ) 
    (*i)->Draw(ShipIm, g);
}


ааа.... то есть мне просто отдельно его отрисовывать и не добавлять в список?

Ship *ship;
ship = new Ship(350, 350);

...

for( GameObject_list::iterator i = objects.begin(); i != objects.end(); i++ ) {
    (*i)->Draw(...);
    ship->Draw();
}

#13
14:49, 10 мар. 2013

=A=L=X=
> То что объявлено в private: секции доступно только из методов текущего класса и
> не будет доступно ни извне ни даже в наследниках. Если ты хочешь запретить
> доступ извне но разрешить доступ в наследниках ты должен использовать
> protected: секцию.

Разобрался, всё работает.

=A=L=X=
> Отнюдь - это не означает что ты все поля и данные производных классов должен
> запихать в базовый - о нет нет, это как раз неправильно. Ты просто должен в
> базовом классе иметь такой набор виртуальных методов чтобы покрыть нужные вещи
> на ура.

а вот здесь я не совсем понял. Правильно ли я понимаю, что мне нужно сделать виртуальные методы SetPricelX(int) и SetPricelY(int) в базовом классе?

class GameObject
{
    int x;
    int y;
public:
virtual void Draw() = 0;

virtual void SetPricelX(int) = 0;
virtual void SetPricelY(int) = 0;
};

=A=L=X=
> В случае если ты 100%-но уверен какой именно объект лежит ты можешь
> использовать C-style cast (он побыстрее, но если ошибиться программа упадет)
ну я думаю я уверен, т.к. прицел имеет только ship :)

Chaos_Optima
> Лучше возьми книжку и выучи язык.
Да я так и делаю, но не всегда всё понятно.

#14
15:17, 10 мар. 2013

Atlant1Q
> class GameObject
> {
> int x;
> int y;
При таком подходе x и у - приватные члены класса GameObject. Если тебе нужен доступ к ним - объяви их как protected. А лучше сделай небольшую структуру типа

struct vec2
{ 
 int x;
 int y;
}
и используй её. При этом даже можно открывать наследникам не саму структуру, а геттер + сеттер для неё.

По поводу приведения типов - да, явно архитектура кривая. Смотри, в базовом классе должны быть самые базовые методы, которые будут у всех объектов, типа draw, update и т.д. Специализированные методы должны быть реализованы уже у каждого наследника отдельно. В самом деле, не будет же у метеорита прицела.
Поэтому оставь так, как было (setPricel - опять таки можно использовать структуру вектора и иметь только один сеттер для прицела). И заведи два списка - один для все игровых объектов, второй для кораблей

std::vector<GameObject*> allObjects;
std::vector<Ship*> allShips;
и обрабатывай корабли отдельно.

Страницы: 1 2 3 Следующая »
ФлеймФорумПрограммирование

Тема в архиве.

Тема закрыта.