Dynamic_cast: в этом случае следует заменить

Существует базовый класс A, который является виртуальным.

class A
{
  ~virtual A() = 0;
};

и более производные классы B, C, D, E...

class B : public A
{
};

class C: public A
{
};

и аналогично для других классов D, E... У нас есть список указателей A

std::list <A*> a_list;

Мы удаляем любой элемент, тип которого неизвестен, например

A *a = a_list.front();

Основываясь на типе остроконечного объекта, мы решаем, что делать... Есть больше возможностей, как это сделать:

A) случай dynamic_cast

Преобразование a в производные типы.

if (dynamic_cast <B*> (a))
{
   //do something (but nothing with a)
}

else if (dynamic_cast <C*> (a))
{
    //do other (but nothing with a)
}

Но использование dynamic_cast указывает на плохой дизайн.

Б) Дополнительный атрибут

Реализован некоторый дополнительный атрибут, например идентификатор объекта;

class A
{
  virtual ~A() = 0;
  virtual short getID() = 0;
};

class B : public A
{
  virtual short getID() {return 1;}
};

class C: public A
{
  virtual short getID() {return 2;}
};

Таким образом, модифицированное условие

switch ( a->getID())
{
   case 1: // do something (but nothing with a)
   case 2: // do other (but nothing with a)
}

Примечание:

Мы не выполняем никаких действий непосредственно с объектом, а на основе его типа делаем какие-то другие вычисления.

Вопросы:

1) Это тот случай, когда нам следует избегать dynamic_cast?

2) есть ли предпочтительное решение (может отличаться от представленного)?

Спасибо за вашу помощь.


person justik    schedule 14.08.2012    source источник
comment
Думаю, здесь можно реализовать шаблон посетителя   -  person Lol4t0    schedule 15.08.2012


Ответы (3)


В соответствии с пунктом 90 стандартов кодирования C++ (Amazon ): Избегайте переключения типа (независимо от того, делаете ли вы это с помощью if-else лестницы и dynamic_cast или оператора switch с функцией getID()). Вместо этого лучше полагаться на полиморфизм через виртуальные функции.

class A
{
public:
  ~virtual A() = 0;

  void fun()  // non-virtual
  { 
     // main algorithm here

     // post-processing step
     post_fun();
  }

  virtual void post_fun() = 0;
};

class B : public A
{
public:
   virtual void post_fun() { /* bla */ }
};

class C: public A
{
public:
   virtual void post_fun() { /* meow */ }
};

A* a = a_list.front();
a->fun(); // will be resolved at run-time to whatever type a points to

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

person TemplateRex    schedule 14.08.2012
comment
никогда — довольно сильная фраза, но случаев, когда переключение типов уместно (или необходимо), очень мало. - person Chad; 15.08.2012
comment
@rhalbersma Но я не хочу выполнять какие-либо действия непосредственно с анализируемым объектом. Таким образом, виртуальная функция getID() применима только для различения типа объекта. Я думаю, что это несколько иной случай. - person justik; 15.08.2012
comment
@justik Это нормально, потому что функция-член fun() может делать все, что вы хотите, ей не нужно использовать данные из B или C. - person TemplateRex; 15.08.2012
comment
@justik Наличие различных действий в fun() в зависимости от состояния объекта a является примером шаблона состояния: en.wikipedia.org/wiki/State_pattern - person TemplateRex; 15.08.2012
comment
@justik Тем не менее, это хороший вопрос, и я проголосую за него позже (сегодня уже достиг своего предела). - person TemplateRex; 15.08.2012
comment
@Ралберсма. Но этот шаблон применим для простых задач. Представим себе очень сложный научный расчет с небольшой разницей в последнем шаге в зависимости от типа объекта. Поэтому очень неэффективно писать такую ​​функцию для двух разных типов, если функциональные различия невелики. - person justik; 15.08.2012
comment
@justik Смотрите мой обновленный ответ: вы можете добавить не виртуальный fun() к A, который выполняет алгоритм (не показан), и последний шаг постобработки под названием post_fun(). Только эту функцию вы затем переопределяете в производных классах B и C. Затем a->fun() по-прежнему делегирует вызов правильного шага постобработки в зависимости от точного типа, на который указывает a. Таким образом, вам нужно написать только небольшое количество кода, в зависимости от того, насколько велика разница на последнем шаге. - person TemplateRex; 15.08.2012
comment
@justik Кстати, этот трюк с невиртуальной функцией, вызывающей виртуальные функции внутри, называется шаблоном метода шаблона: en.wikipedia.org/wiki/Template_method_pattern - person TemplateRex; 15.08.2012
comment
@ rhalbersma: Это может быть разумным решением. Готовя чай, я думал о том же: разделить вычисление на типозависимую и типозависимую части. Спасибо за вдохновение... - person justik; 15.08.2012
comment
@rhalbersma: Спасибо. Goede Nacht в Утрехте :-) - person justik; 15.08.2012

В большинстве случаев, когда вам нужно использовать что-то B-специфичное (с сохранением имени), вы должны хранить B * (или, скорее, shared_ptr<B>) вместо A *. Во всех остальных случаях прячьте все за полиморфизмом.

Рассмотрим следующую иерархию:

class Animal 
{
public:
    Animal() {}
    virtual ~Animal() = 0;
    virtual void Breathe();
};

class Bird : public Animal
{
public:
    Bird() {}
    virtual ~Bird() {}
    virtual void Breathe() {...}
    virtual void Fly() {...}
};

и представьте, что вы храните Animal *s - вам не следует сейчас вызывать Fly(). Если вам нужно вызвать его, сохраните Bird * с самого начала. Но все животные должны дышать — поэтому функция наследуется от базового класса.


Подводя итог: если вам нужно сделать что-то специфичное для Child, сохраните указатель на Child, а не на Base.

person Griwes    schedule 14.08.2012

Обычно динамическое приведение выполняется, чтобы заставить определенный тип объекта работать с ними.

struct base
{
    virtual void a() = 0;
};

struct foo : base
{
    virtual void a() { ... }
    void specificFooMethod();
};

struct bar : base
{
    virtual void a() { ... }
    void specificBarMethod();
};

base * pBase = ...;
pBase->a(); // no casting required here
if ( foo * p = dynamic_cast<foo*>(pBase) )
{
    p->specificFooMethod();
}
else if ( bar * p = dynamic_cast<bar*>(pBase) )
{
    p->specificBarMethod();
}

определенные* методы не являются виртуальными и не могут быть доступны базовому интерфейсу без приведения типов. Поэтому dynamic_cast следует использовать, когда вам действительно нужен объект определенного типа, который имеет дополнительные функции/свойства, которые нельзя переместить на абстрактный уровень в базовом классе.

person Torsten    schedule 14.08.2012