Luc*_*ier 5 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. */
Run Code Online (Sandbox Code Playgroud)
数据结构用于产生非循环有向图.C类充当包含代数任务的实际数据的"叶子".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
}
}
Run Code Online (Sandbox Code Playgroud)
甚至更糟:
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
它绝对是面向对象的.它确实使用继承.但感觉就像我只是把所有的安慰都放在了OOP让我登机,并决定再次使用原始指针和位移.谷歌搜索主题,我发现很多人说上下推都是糟糕的设计或不好的做法.我完全相信这是真的,但我想知道为什么.我不能改变大部分代码,因为它是一个更大的项目的一部分,但我想知道如何在我将来设计程序时反击这种情况.
我的问题:
这里有很多关于dynamic_cast
SO的问题。我只读了一些,并且在我自己的代码中也不经常使用该方法,因此我的回答反映了我对这个主题的看法,而不是我的经验。小心。
(1.) 除了看起来很糟糕之外,为什么向上/向下铸造被认为是糟糕的设计?
(3.) 有没有什么经验法则可以避免像我上面解释的那样的设计?
在阅读 Stroustrup C++ FAQ 时,我认为有一个中心信息:不要相信那些说永远不要使用某种工具的人。相反,使用正确的工具来完成手头的任务。
然而,有时两种不同的工具可能具有非常相似的目的,这里也是如此。您基本上可以使用dynamic_cast
throughvirtual
函数重新编码任何功能。
那么什么时候才是dynamic_cast
合适的工具呢?(另请参阅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
}
}
Run Code Online (Sandbox Code Playgroud)
要重写它,请在基类中添加一个(可能是纯的)虚函数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, ...
Run Code Online (Sandbox Code Playgroud)
然后在你的函数中调用它或者可能编写一个更简洁的函数模板
template<typename T>
void addToCurrentElement(T const* t)
{
currentElement->add(t);
}
Run Code Online (Sandbox Code Playgroud)
我想说这是标准方法。如前所述,缺点可能是对于纯虚函数,您需要N*N
重载,而重载N
可能就足够了(例如,如果只A::add
需要特殊处理)。
(2.)dynamic_cast 慢吗?
当考虑整个网络状态的大多数答案时,是的,动态转换似乎很慢,例如参见此处。然而,我没有实际经验来支持或反驳这一说法。