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

Пару дней назад я спросил несколько разъяснения по наследованию, концепцию, которую я все еще пытаюсь понять. Вот следующий вопрос, так как я все еще сталкиваюсь с проблемами.

В моем проекте у меня есть 2 типа объектов: рука и лицо, оба наследующие от базового класса BodyPart. BodyPart выглядит примерно так:

class BodyPart
{
  public:
  typedef boost::shared_ptr<BodyPart> BodyPartPtr;

  BodyPart();
  virtual ~BodyPart();

  private:
  int commonMember1;
  double commonMember2;

  public:
  int commonMethod1();
  int CommonMethod2();
}

в то время как рука выглядит примерно так:

class Hand : public BodyPart
{
  public:
  Hand();
  ~Hand();

  private:
  int numFingers;
  double otherVar;

  public:
  int getNumFingers();
  void printInfo();
}

У меня также есть вектор элементов BodyPart

std::vector<BodyPart::BodyPartPtr> cBodyParts;

состоит из объектов «Рука» или «Голова». В предыдущем вопросе мне сказали, что этот подход имеет смысл, мне просто нужно было преобразовать базовый класс в производный, используя boost static_pointer_cast

Теперь проблема в том, что для некоторых объектов в векторе я не знаю, являются ли они Hand или Head, поэтому в какой-то момент в моем коде я могу иметь в cBodyParts некоторые элементы Hand, некоторые элементы Head, а также некоторые BodyPart элементов. После некоторого дальнейшего анализа я могу правильно классифицировать последний как Hand или Head и соответствующим образом изменить элементы в векторе, но я понятия не имею, как это сделать. Должен ли я просто удалить элемент класса case и создать производный элемент с тем же свойством? Должен ли я просто избежать наследования в таком случае?

Заранее спасибо за помощь


person Community    schedule 09.01.2013    source источник
comment
Что ж, как вы только что убедились, кастинг не имеет особого смысла. Вместо этого используйте виртуальные функции-члены в базовом классе, чтобы предоставить желаемую абстрактную функциональность.   -  person Kerrek SB    schedule 09.01.2013


Ответы (4)


РЕДАКТИРОВАТЬ: я дополнил примеры, чтобы сделать их более понятными.

Ретрансляция на бросках обычно является признаком плохого дизайна. У гипса есть свое место, но, похоже, это не так.

Вам нужно спросить себя, что вы хотите делать с объектами, хранящимися в cBodyParts. Конечно, вы будете делать разные вещи с Hand или с Head, но вы, вероятно, можете как-то их абстрагировать: это то, что делают виртуальные функции. Итак, вдобавок к тому, что вы уже написали для своих классов, вам просто понадобится дополнительная виртуальная функция в них:

class BodyPart
{
  // Same as you wrote, plus:
public:
  virtual void InitialisePart() = 0; // Pure virtual: each body part must say how to process itself
  virtual void CalibrateJoints() {} // Override it only if the body part includes joints
}

class Head : public BodyPart
{
  // Same as you wrote, plus:
public:
  virtual void InitialisePart() {
    // Code to initialise a Head
  }
  // Since a Head has no joints, we don't override the CalibrateJoints() method
}

class Hand : public BodyPart
{
  // Same as you wrote, plus:
public:
  virtual void InitialisePart() {
    // Code to initialise a Hand
  }
  virtual void CalibrateJoints() {
    // Code to calibrate the knuckles in the hand
  }
}

И тогда вам больше не нужны никакие приведения. Например:

for (BodyPart::BodyPartPtr part : cBodyParts) {
  part->InitialisePart();
  part->CalibrateJoints(); // This will do nothing for Heads
}

Как видите, вообще никаких бросков и все будет нормально работать. Эта схема расширяема; если позже вы решите, что вам нужны дополнительные классы, наследующие от BodyPart, просто напишите их, и ваш старый код будет работать корректно:

class Torso : public BodyPart
{
public:
  virtual void InitialisePart() {
    // Code to initialise a Torso
  }
  // The Torso has no joints, so no override here for CalibrateJoints()

  // Add everything else the class needs
}

class Leg : public BodyPart
{
public:
  virtual void InitialisePart() {
    // Code to initialise a Leg
  }
  virtual void CalibrateJoints() {
    // Code to calibrate the knee
  }

  // Add everything else the class needs
}

Теперь вам не нужно изменять код, который вы написали ранее: цикл for выше будет работать правильно, а Torso или Leg он найдет без необходимости обновления.

person Gorpik    schedule 09.01.2013
comment
Спасибо Горпик. Этот подход определенно хорош и элегантен, но я не понимаю, как он справляется с необходимостью конвертировать объекты из базового класса в производные, как только я узнал, что на самом деле представляют собой мои векторные элементы. - person ; 09.01.2013
comment
@Stocastico Вам нужно избавиться от необходимости конвертировать из базы в производную. - person Peter Wood; 09.01.2013
comment
@Stocastico Главная привлекательность этого метода заключается в том, что вам не вообще нужно преобразовывать в производный класс. Для чего вы хотите конвертировать? - person Gorpik; 09.01.2013
comment
Ну, потому что в начале обработки бывают случаи, когда я не знаю, является ли объект рукой или головой, поэтому я просто сохраняю его в векторе как BodyPart. Дальнейший анализ позволяет мне выяснить, что это за объект, и именно в этот момент я должен выполнить преобразование. Я думаю, как говорит @Peter Wood, я должен избавиться от этой необходимости - person ; 09.01.2013
comment
@Stocastico Если вы еще раз прочитаете то, что только что написали, вы увидите, что не ответили на мой вопрос. Вам не нужно конвертировать только потому, что вы знаете, что ваш BodyPart на самом деле является Hand: вам нужно конвертировать, если есть что-то, что вы должны сделать и не можете без конвертации. Есть ли что-то, что вам нужно сделать, чего нельзя сделать с помощью виртуальных функций? - person Gorpik; 09.01.2013
comment
Хорошо, теперь понятно, извините. Ну, предположим, я хочу установить значения некоторых переменных-членов класса Hand, а эти переменные принадлежат только классу Hand (например, int thumbLength). Как я мог это сделать? Определение чисто виртуального метода в базовом классе потребовало бы переопределения его в каждом производном классе... это не кажется хорошим подходом, не так ли? - person ; 09.01.2013
comment
@Stocastico На самом деле, это правильный подход. Что ж, если это будет делаться только в некоторых классах, вы можете использовать обычный (не чистый) виртуальный метод. Я расширю примеры в ответе, чтобы вы могли лучше это увидеть. - person Gorpik; 09.01.2013
comment
@Stocastico Если вы хотите изменить отдельные части тела, создайте контроллеры для каждой части. Итак, повторите каждую часть тела, говоря makeControl, и она создаст элемент управления, соответствующий ее атрибутам. Затем используйте это, чтобы изменить его. - person Peter Wood; 09.01.2013
comment
Хорошо, Горпик, спасибо большое. Я собираюсь отметить это как решенное, так как теперь я получаю картину. Однако последний вопрос: предположим, что вектор cBodyParts заполняется путем отталкивания объекта Hand, а затем объекта BodyPart. После этого я узнаю, что 2-й объект на самом деле Hand, и я хочу установить его numFingers. Есть ли способ сделать это, если я создал этот второй объект, используя new BodyPart(), таким образом, не выделяя память для переменной numFinger? - person ; 09.01.2013
comment
@Stocastico Нет. Если вы создадите его как обычный BodyPart, он не может быть Hand; где-то конструктивная ошибка. Фактически, вы, вероятно, не сможете создать BodyPart, точно так же, как у нас, людей, нет общих частей тела; вы всегда должны создавать настоящие, конкретные детали. - person Gorpik; 09.01.2013
comment
Ok. Как я думал. Спасибо :-) - person ; 09.01.2013

Бедренная кость соединена с бедренной костью...

Я так понимаю, у вас есть какой-то состав всех частей тела, возможно, Body класса.

Что вы хотите, чтобы тело делало?

  • Визуализировать себя
  • сериализовать
  • Выведите его объем, или ограничивающую рамку, или какую-либо другую метрику.
  • Переориентируется в ответ на ввод
  • Ответ на инверсно-кинематической физической модели

Список, наверное, можно продолжить. Если вы точно знаете, что вы хотите, чтобы Body делал, вы можете поместить эту функцию в базовый класс BodyPart, а Body выполнить итерацию по составной иерархической структуре всех связанных частей тела, вызывая, например, render.

Альтернативой является использование Visitor, который фактически является способом динамического добавления методов к статической иерархии наследования.

person Peter Wood    schedule 09.01.2013

Как указал Керрек С.Б., это вообще невозможно, но ради ответа на фактический вопрос dynamic_cast это то, что вы ищете.

person filmor    schedule 09.01.2013

Используйте виртуальные функции, они значительно упростят вашу задачу.

В противном случае вы можете добавить несколько методов для различения разных типов. Однако делайте это только в том случае, если вы не можете сделать это другим способом, т.е. если вы не можете сделать это через виртуальные функции.

Пример 1:

// in BodyPart; to be reimplemented in derived classes
virtual bool isHand() const { return false; }
virtual bool isHead() const { return false; }

// in Hand (similar to what will be in Head)
bool isHand() const { return true; }

// How to use:
BodyPart::pointer ptr = humanBodyVector[42]; // one item from the array
if(ptr->isHand())
    processHand(/*cast to hand*/)
else if(ptr->isHead())
    // ...

Пример 2: пусть производные классы обрабатывают приведение

// in BodyPart; to be reimplemented in derived classes
virtual Hand* toHand() const { return 0; }
virtual Head* toHead() const { return 0; }

// in Hand (similar to what will be in Head)
Hand* toHand() const { return this; }
person Synxis    schedule 09.01.2013
comment
хотя это плохая реализация, поскольку isHand подразумевает, что базовый класс фактически знает свои производные классы. Виртуальные функции должны быть связаны только с реальным классом, например, dismember() или hasBloodFlow() или move(), то есть то, что связано с BodyPart - person Default; 09.01.2013
comment
Бывают случаи, когда базовый класс знает о своих производных классах или, по крайней мере, о некоторых из своих производных классов. Второй метод можно увидеть, например, в некоторых частях Qt. - person Synxis; 09.01.2013
comment
Это может иметь смысл в том случае, если иерархия точно не будет расширена. С базовым классом под названием BodyPart я не думаю, что мы в таком случае. - person Gorpik; 09.01.2013
comment
@Synxis То, что это делают другие, не означает, что это правильно :) - person Default; 09.01.2013
comment
@GorPik Не совсем согласен. Я согласен с вами в том, что иерархия наверняка не будет расширена. Но с другой стороны: все части тела известны, и лишний член не получить ;) - person Synxis; 09.01.2013
comment
@Synxis: Можете ли вы перечислить все части тела и быть уверенным, что ничего не пропустили? Кроме того, вы не должны писать классы, которые вам не нужны. Если вам не нужен класс Elbow, не пишите его сейчас. Но, возможно, в будущем вы это сделаете, расширив иерархию. - person Gorpik; 09.01.2013
comment
Спасибо Синксис. Хотя бы для ясности. Предположим, что ptr в вашем примере one является членом BodyPart, но с помощью некоторых других методов я узнаю, что это должен быть объект Hand. Как я могу изменить ptr на руку? Должен ли я использовать динамическое приведение, как это предлагается в ответе @filmor? - person ; 09.01.2013