Is dynamic_casting through inheritance hierarchy a bad practice?

I have the following data structure:

class Element { std::string getType(); std::string getId(); virtual std::vector<Element*> getChildren(); } class A : public Element { void addA(const A *a); void addB(const B *b); void addC(const C *c); std::vector<Element*> getChildren(); } class B : public Element { void addB(const B *b); void addC(const C *c); std::vector<Element*> getChildren(); } class C : public Element { int someActualValue; } /* The classes also have some kind of container to store the pointers and * child elements. But let keep the code short. */ 

The data structure is used to define an acyclic oriented graph. Class C acts as a β€œsheet” containing factual data for algebraic tasks. A and B contain other information such as names, types, rules, my favorite color and weather forecast.

I want to program a function in which a window appears, and you can navigate through an existing structure. Along the way, I want to show the path that the user took with a beautiful flowchart that is available to go into the hierarchy. Based on the current Graph-Node graph (which can be A, B or C), some information should be calculated and displayed.

I thought I could just create a std :: vector of type Element * and use the last element as the active element I'm working with. I thought this was a pretty good approach, as it uses inheritance that already exists, and keeps the code to me very little.

But I have many situations like this:

 Element* currentElement; void addToCurrentElement(const C *c) { if(A *a = dynamic_cast<A*>(currentElement)) { //doSomething, if not, check if currentElement is actually a B } } 

Or worse:

 vector<C*> filterForC's(A* parent) { vector<Element*> eleVec = parent.getChildren(); vector<C*> retVec; for(Element* e : eleVec) { if (e.getType() == "class C") { C *c = dynamic_cast<C*>(e); retVec.append(c); } } } 

It is definitely object oriented. It definitely uses inheritance. But it seems to me that I just threw all the amenities, the OOP gives me overboard and decided to use unprocessed pointers and extra battles again. Looking at the topic, I found that many people say that casting up / down is a bad design or a bad practice. I fully believe that this is true, but I want to know why. I cannot change most of the code because it is part of a larger project, but I want to know how to counter something like this when I develop the program in the future.

My questions:

  • Why is casting up / down considered a bad design besides the fact that it looks awful?
  • Is slow_cast slow?
  • Are there any rules of thumb how can I avoid a design similar to the one I explained above?
+5
source share
1 answer

dynamic_cast has a lot of SO questions here. I read only a few, and also do not often use this method in my own code, so my answer reflects my opinion on this issue, and not my experience. Beware.

(1.) Why is casting up / down considered a poor design besides the fact that it looks awful?

(3.) Are there any rules of thumb for how I can avoid a design like the one described above?

When reading the Stroustrup C ++ frequently asked questions, imo has one central message: do not trust people who never use any tool. Rather, use the right tool for this task.

Sometimes, however, two different tools can have a very similar purpose, and so it is. Basically you can recode any functionality using dynamic_cast functions via virtual .

So, when is dynamic_cast right tool? (see also. What is the correct use case for dynamic_cast? )

  • One of the possible situations is that you have a base class that you cannot extend, but nevertheless you need to write overloaded code. With dynamic casting, you can make it non-invasive.

  • Another is where you want to save the interface, i.e. pure virtual base class, and do not want to implement the corresponding virtual function in any derived class.

Often, however, you prefer to rely on a virtual function - if only on reduced ugliness. In the future, this is safer: dynamic casting may fail and terminate your program; a virtual function call (usually) will not.

In addition, implemented in terms of pure functions, you will not forget to update it in all the necessary places when adding a new derived class. On the other hand, dynamic typing can be easily forgotten in the code.

The virtual function version of your example

Here is another example:

 Element* currentElement; void addToCurrentElement(const C *c) { if(A *a = dynamic_cast<A*>(currentElement)) { //doSomething, if not, check if currentElement is actually a B } } 

To rewrite it, add (possibly pure) virtual functions add(A*) , add(B*) and add(C*) , which you overload in derived classes, in your database.

 struct A : public Element { virtual add(A* c) { /* do something for A */ } virtual add(B* c) { /* do something for B */ } virtual add(C* c) { /* do something for C */ } }; //same for B, C, ... 

and then call it in your function or maybe write a more concise function template

 template<typename T> void addToCurrentElement(T const* t) { currentElement->add(t); } 

I would say that this is a standard approach. As already mentioned, the drawback may be that for pure virtual functions you will need N*N overloads, where possibly N can be enough (say, if only A::add requires special processing).

Other alternatives may use RTTI , CRTP pattern, erase type, and possibly more.


(2.) Is slow_cast slow?

When considering most of the answers across the entire network state, yes, the dynamic effect seems slow, for example, here . However, I have no practical experience to support or cancel this expression.

+1
source

All Articles