我有以下数据结构:
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让我登机,并决定再次使用原始指针和位移。谷歌搜索主题,我发现很多人说上下推都是糟糕的设计或不好的做法。我完全相信这是真的,但我想知道为什么。我不能改变大部分代码,因为它是一个更大的项目的一部分,但我想知道如何在将来设计程序时对付这种情况。
我的问题:
答案 0 :(得分:1)
这里有关于dynamic_cast
的很多问题。我只阅读了一些,并且在我自己的代码中经常不使用该方法,所以我的回答反映了我对这个主题的看法,而不是我的经验。小心。
(1。)除了它看起来很可怕之外,为什么上下推都被认为是糟糕的设计?
(3。)是否有任何经验法则可以避免像我上面解释的那样的设计?
在阅读Stroustrup C ++ FAQ时,有一条中心信息:不要相信那些说从不使用某种工具的人。相反,使用正确的工具来完成手头的任务。
然而,有时两种不同的工具可能具有非常相似的目的,因此它也是如此。您基本上可以使用dynamic_cast
到virtual
函数重新编码任何功能。
那么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
需要特殊处理)。
其他替代方案可能会使用RTTI,CRTP模式,类型擦除,甚至更多。
(2。)dynamic_cast缓慢吗?
当考虑整个网状态中的大多数答案时,是的,动态演员似乎很慢,例如here。 然而,我没有实际经验来支持或否定这一陈述。