Inheritence and usage of dynamic_cast

2019-08-27 14:33发布

问题:

Suppose I have 3 classes as follows (as this is an example, it will not compile!):

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{ ... }
};

I want to create an instance of Derived1 or Derived2 depending on some setting that is not available until run-time.

As I cannot determine the derived type until run-time, then do you think the following is bad practice?...

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;
};

I have generally read that usage of dynamic_cast is bad, but as I said, I don't know which type to create until run-time. Please help!

回答1:

Using dynamic_cast is not bad practice per se. It's bad practice to use it inappropriately, i.e. where it's not really needed.

It's also a bad practice to use it this way:

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

Reason: dynamic_cast(b) may return NULL.

When using dynamic_cast, you have to be extra careful, because it's not guaranteed, that b is actually of type Derived1 and not 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!
}

You have to check if dynamic_cast is actually successful. There are two ways of doing it: checking it before and after the cast. Before the cast you can check if the pointer you're trying to cast is actually the one you expect via 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
}

Checking it post-factum is actually a bit simpler:

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

I'd also say it's a bad practice to try and save typing while programming in C++. There are many features in C++ than seem to be completely fine to be typed shorter (i.e. makes you feel 'that NULL will never happen here'), but turn out to be a pain in the ass to debug afterwards. ;)



回答2:

Why not delay the moment at which you "throw away" some if the type information by assigning a pointer to derived to a pointer to base:

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:

The point of virtual functions is that once you have the right kind of object, you can call the right function without knowing which derived class this object is -- you just call the virtual function, and it does the right thing.

You only need a dynamic_cast when you have a derived class that defines something different that's not present in the base class, and you need/want to take the extra something into account.

For example:

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

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

Now, if you just want to call do_something(), a dynamic_cast is completely unnecessary. For example, you can have a collection of Base *, and just invoke do_something() on every one, without paying any attention to whether the object is really a Base or a Derived.

When/if you have a Base *, and you want to invoke do_something_else(), then you can use a dynamic_cast to figure out whether the object itself is really a Derived so you can invoke that.



回答4:

Some other things you might like to consider to avoid the use of dynamic_cast

From Effective C++ (Third Edition) - Item 35 Alternatives to virtual functions -

  1. 'Template Method pattern' via Non-Vitual Interface (NVI). Making the virtual functions private/protected with a public method 'wrapper' - allows you to enforce some other workflow of stuff to do before and after the virtual method.
  2. 'Strategy pattern' via function pointers. Pass in the extra method as a function pointer.
  3. 'Strategy pattern' via tr1::function. similar to 2. but you could provide whole classes with various options
  4. 'Strategy pattern' classic. Seperate Strategy from main class - push the virtual functions into another hierarchy.


回答5:

There is a pattern named Factory Pattern that would fit this scenario. This allows you to return an instance of the correct class based on some input parameter.

Enjoy!



回答6:

What is wrong with:

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

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

Or am I missing something?

And can OI suggest that when posting questions like this you (and others) name your classes A, B, C etc. and your functions things like f1(), f2() etc. It makes life a lot easier for people answering your questions.



回答7:

One way to avoid dynamic_cast is to have a virtual trampoline function "SpecialDoSomething" whose derived polymorphic implementation calls that particular derived class's "SpecialDxDoSomething()" which can be whatever non-base class name you desire. It can even call more than one function.