是否通过继承层次结构dynamic_casting不良做法
Is dynamic_casting through inheritance hierarchy bad practice?
我得到了以下数据结构:
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 类充当包含代数任务实际数据的"叶子"。A和B包含其他信息,如名称,类型,规则,我最喜欢的颜色和天气预报。
我想对一个功能进行编程,其中会弹出一个窗口,您可以在已经存在的结构中导航。在途中,我想用一些漂亮的流程图显示用户所采用的路径,可以单击该流程图以返回到层次结构中。基于当前访问的图形节点(可以是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 给我的所有舒适感都扔到了一边,决定再次使用原始指针和位移。谷歌搜索这个主题,我发现很多人说上下选角是糟糕的设计或糟糕的做法。我完全相信这是真的,但我想知道为什么。我不能更改大部分代码,因为它是一个更大项目的一部分,但我想知道如何在将来设计程序时应对这种情况。
我的问题:
- 为什么上下铸造被认为是糟糕的设计,除了它看起来很糟糕的事实?
- dynamic_cast慢吗?
- 有什么经验法则可以避免像我上面解释的那样的设计吗?
关于 SO 这里有很多关于dynamic_cast
的问题。我只读了几本,也不经常在我自己的代码中使用这种方法,所以我的回答反映了我对这个主题的看法,而不是我的经验。小心。
(1.)为什么上下铸造被认为是糟糕的设计,除了它看起来很糟糕的事实?
(3.)是否有任何经验法则可以避免像我上面解释的设计?
在阅读Stroustrup C++常见问题解答时,我有一个中心信息:不要相信那些说永远不要使用某种工具的人。相反,使用正确的工具来完成手头的任务。
然而,有时,两种不同的工具可能具有非常相似的目的,这里也是如此。您基本上可以使用dynamic_cast
到virtual
函数重新编码任何功能。
那么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
}
}
要重写它,请在您的基中添加一个(可能是纯的)虚函数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慢吗?
当考虑整个网络状态中的大多数答案时,是的,动态转换似乎很慢,例如请参阅此处。然而,我没有实际经验来支持或反驳这种说法。
- 为什么"using System;"不被视为不良做法?
- 提升不良词法强制转换:将字符串转换为无符号长整型时,无法将源类型值解释为目标
- 删除复制构造函数的 Intel 13.1.2 中不良C++行为的解决方法
- 如果 constexpr 和依赖假static_assert形成不良?
- 不从成员函数C 存储返回值认为不良习惯
- shared_from__this,asio的不良弱ptr例外
- 试图初始化矩阵C 时的不良同种
- 创建以不良输出结束
- 检测`boost :: Math ::工具:: brent_find_minima()的不良输入
- C HTTP POST 400不良请求
- OPENCV错误:不良的参数(字节必须为16、32或64)在简介Descriptorextractorimpl中
- Casting a const void*
- 与全球矢量的不良同种
- 限制C++中允许的模板参数是否被认为是不良样式?
- 从触摸回调访问向量元素的方法时,不良访问错误
- 不良指针和乱码的数据
- CGAL:Hausdorff距离不良Alloc
- 如果您的团队同意一组别名,则使用类型别名进行STL容器不良练习
- 异常的不良TypeInfo名称
- 400 Casablanca cpprestsdk的不良要求