动态广播是通过继承层次结构糟糕的做法吗?

时间:2015-06-18 20:01:49

标签: c++ oop inheritance casting

我有以下数据结构:

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's keep the code short. */

数据结构用于产生非循环有向图。 C类充当&#34; leaf&#34;包含代数任务的实际数据。 A和B保存其他信息,如姓名,类型,规则,我最喜欢的颜色和天气预报。

我想编写一个功能,弹出一个窗口,您可以浏览已有的结构。在途中我想用一些漂亮的流程图显示用户使用的路径,可以点击它返回层次结构。根据当前访问的Graph-Node(可以是A,B或C),必须计算和显示一些信息。

我想我可以创建一个类型为Element *的std :: vector,并使用最后一项作为我使用的活动元素。我认为这是一个非常好的方法,因为它利用了已经存在的继承并保持我需要的代码非常小。

但我有很多这样的情况:

Element* currentElement;

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

甚至更糟:

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

它绝对是面向对象的。它确实使用继承。但感觉就像我只是把所有的安慰都放在了OOP让我登机,并决定再次使用原始指针和位移。谷歌搜索主题,我发现很多人说上下推都是糟糕的设计或不好的做法。我完全相信这是真的,但我想知道为什么。我不能改变大部分代码,因为它是一个更大的项目的一部分,但我想知道如何在将来设计程序时对付这种情况。

我的问题:

  1. 除了看起来很可怕之外,为什么上下推都被认为是糟糕的设计呢?
  2. 动态广播是否缓慢?
  3. 我有什么经验法则可以避免像我上面解释过的那样的设计吗?

1 个答案:

答案 0 :(得分:1)

这里有关于dynamic_cast的很多问题。我只阅读了一些,并且在我自己的代码中经常不使用该方法,所以我的回答反映了我对这个主题的看法,而不是我的经验。小心。

  

(1。)除了它看起来很可怕之外,为什么上下推都被认为是糟糕的设计?

     

(3。)是否有任何经验法则可以避免像我上面解释的那样的设计?

在阅读Stroustrup C ++ FAQ时,有一条中心信息:不要相信那些说从不使用某种工具的人。相反,使用正确的工具来完成手头的任务。

然而,有时两种不同的工具可能具有非常相似的目的,因此它也是如此。您基本上可以使用dynamic_castvirtual函数重新编码任何功能。

那么dynamic_cast什么时候才是正确的工具? (另见What is the proper use case for dynamic_cast?

  • 一种可能的情况是,如果你有一个你无法扩展的基类,但仍然需要编写类似重载的代码。通过动态铸造,您可以做到非侵入性。

  • 另一个是您希望保留接口的地方,即纯虚拟基类,并且不想在任何派生类中实现相应的虚函数。

然而,通常,您宁愿依赖虚拟功能 - 如果只是为了减少丑陋。此外,它更安全:动态强制转换可能会失败并终止您的程序,虚拟函数调用(通常)不会。

此外,根据纯函数实现,当您添加新的派生类时,不会忘记在所有必需的位置更新它。另一方面,代码中很容易忘记动态强制转换。

您的示例的虚函数版本

以下是再次举例:

Element* currentElement;

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

要重写它,在你的基础中添加一个(可能是纯的)虚拟函数add(A*)add(B*)add(C*),你在派生类中重载。

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, ...

然后在你的函数中调用它,或者可能写一个更简洁的函数模板

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

我说这是标准方法。如上所述,缺点可能是对于纯虚函数,您需要N*N重载,可能N可能就足够了(例如,如果只有A::add需要特殊处理)。

其他替代方案可能会使用RTTICRTP模式,类型擦除,甚至更多。


  

(2。)dynamic_cast缓慢吗?

当考虑整个网状态中的大多数答案时,是的,动态演员似乎很慢,例如here。 然而,我没有实际经验来支持或否定这一陈述。