Наследование и использование dynamic_cast - PullRequest
0 голосов
/ 18 июня 2010

Предположим, у меня есть 3 класса следующим образом (как пример, он не скомпилируется!):

class Base
{
public:
   Base(){}
   virtual ~Base(){}
   virtual void DoSomething() = 0;
   virtual void DoSomethingElse() = 0;
};

class Derived1
{
public:
   Derived1(){}
   virtual ~Derived1(){}
   virtual void DoSomething(){ ... }
   virtual void DoSomethingElse(){ ... }
   virtual void SpecialD1DoSomething{ ... }
};

class Derived2
{
public:
   Derived2(){}
   virtual ~Derived2(){}
   virtual void DoSomething(){ ... }
   virtual void DoSomethingElse(){ ... }
   virtual void SpecialD2DoSomething{ ... }
};

Я хочу создать экземпляр Derived1 или Derived2 в зависимости от некоторых настроек, которые недоступны до времени выполнения.

Поскольку я не могу определить производный тип до времени выполнения, вы думаете, что следующее - плохая практика? ...

class X
{
public:
   ....

   void GetConfigurationValue()
   {
      ....
      // Get configuration setting, I need a "Derived1"
      b = new Derived1();

      // Now I want to call the special DoSomething for Derived1
      (dynamic_cast<Derived1*>(b))->SpecialD1DoSomething();      
   }
private:
   Base* b;
};

Я обычно читал, что использование dynamic_cast плохое, но, как я уже сказал, я не знаю, какой тип создать до времени выполнения. Пожалуйста, помогите!

Ответы [ 7 ]

6 голосов
/ 18 июня 2010

Почему бы не отложить момент, когда вы «выбрасываете» некоторые данные типа, назначая указатель на производный указатель на базу:

void GetConfigurationValue()
{
  // ...
  // Get configuration setting, I need a "Derived1"
  Derived1* d1 = new Derived1();
  b = d1;

  // Now I want to call the special DoSomething for Derived1
  d1->SpecialD1DoSomething();
}
3 голосов
/ 18 июня 2010

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

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

Например:

struct Base { 
    virtual void do_something() {}
};

struct Derived : Base { 
    virtual void do_something() {} // override dosomething
    virtual void do_something_else() {} // add a new function
};

Теперь, если вы просто хотите позвонить do_something(), dynamic_cast совершенно не нужен. Например, вы можете иметь коллекцию Base * и просто вызывать do_something() для каждого, не обращая никакого внимания на то, является ли объект действительно Base или Derived.

Когда / если у вас есть Base *, и вы хотите вызвать do_something_else(), , тогда , вы можете использовать dynamic_cast, чтобы выяснить, действительно ли сам объект Derived, так Вы можете призвать это.

2 голосов
/ 18 июня 2010

Некоторые другие вещи, которые вы могли бы рассмотреть, чтобы избежать использования dynamic_cast

Из Effective C ++ (третье издание) - Элемент 35 Альтернативы виртуальным функциям -

  1. 'Шаблонный методшаблон 'через не витальный интерфейс (NVI).Делая виртуальные функции частными / защищенными с помощью общедоступного метода «обертка» - позволяет вам предписывать некоторые другие рабочие процессы, которые нужно выполнять до и после виртуального метода.
  2. «Шаблон стратегии» с помощью функциональных указателей.Передайте дополнительный метод в качестве указателя на функцию.
  3. 'Шаблон стратегии' через tr1 :: function.похож на 2. но вы можете предоставить целые классы с различными вариантами
  4. Классический шаблон стратегии.Отделите стратегию от основного класса - перенесите виртуальные функции в другую иерархию.
2 голосов
/ 18 июня 2010

Использование dynamic_cast само по себе не является плохой практикой.Это плохая практика - использовать ее не по назначению, то есть там, где это действительно не нужно.

Это также плохая практика использовать его следующим образом:

(dynamic_cast<Derived1*>(b))->SpecialD1DoSomething();  

Причина: dynamic_cast (b) может возвращать NULL.

При использовании dynamic_cast вы должны бытьочень осторожно, поскольку не гарантируется, что b на самом деле имеет тип Derived1, а не Derived2:

void GenericFunction(Base* p)
{
    (dynamic_cast<Derived1*>(b))->SpecialD1DoSomething();
}

void InitiallyImplementedFunction()
{
   Derived1 d1;
   GenericFunction(&d1); // OK... But not for long. 
   // Especially, if implementation of GenericFunction is in another library
   // with not source code available to even see its implementation 
   // -- just headers
}    

void SomeOtherFunctionProbablyInAnotherUnitOfCompilation()
{
   Derived2 d2;
   GenericFunction(&d2); // oops!
}

Вы должны проверить, действительно ли dynamic_cast успешен.Есть два способа сделать это: проверить это до и после приведения.Перед приведением вы можете проверить, является ли указатель, который вы пытаетесь привести, именно тем, который вы ожидаете через RTTI:

if (typeid(b) == typeid(Derived1*))
{
   // in this case it's safe to call the function right 
   // away without additional checks
   dynamic_cast<Derived1*>(b)->SpecialD1DoSomething();
}
else
{
  // do something else, like try to cast to Derived2 and then call
  // Derived2::SpecialD2DoSomething() in a similar fashion
}

Проверка этого постфактум на самом деле немного проще:

Derived1* d1 = dynamic_cast<Derived1*>(b);
if (d1 != NULL)
{
   d1->SpecialD1DoSomething();
}

Я бы также сказал, что пытаться сохранять набор текста при программировании на C ++ - плохая практика.В C ++ есть много функций, которые кажутся вполне подходящими для того, чтобы набирать их короче (т. Е. Дает вам ощущение «что NULL здесь никогда не случится»), но оказывается, что задница после отладки - это боль в заднице.;)

1 голос
/ 18 июня 2010

Один из способов избежать dynamic_cast состоит в том, чтобы иметь функцию виртуального батута «SpecialDoSomething», чья производная полиморфная реализация вызывает функцию «SpecialDxDoSomething ()» конкретного производного класса, которая может быть любым именем неосновного класса, которое вы пожелаете.Он может даже вызывать более одной функции.

1 голос
/ 18 июня 2010

Существует шаблон с именем Заводской шаблон , который подходит для этого сценария.Это позволяет вам возвращать экземпляр правильного класса на основе некоторого входного параметра.

Наслаждайтесь!

1 голос
/ 18 июня 2010

Что не так с:

Base * b;
if( some_condition ) {
   b = new Derived1;
}
else {
   b = new Derived2;
}

if ( Derived2 * d2 = dynamic_cast <Derived2 *>( b ) ) {
    d2->SpecialD2DoSomething();
}

Или я что-то упускаю?

И могу ли OI предложить, чтобы при публикации таких вопросов вы (и другие) называли свои классы A, B, C и т. Д. И ваши функции, такие как f1 (), f2 () и т. Д. Это облегчает жизнь людям, отвечающим на ваши вопросы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...