避免在继承的树类中进行下转换
Avoid downcasting in an inherited tree class
我对C++还比较陌生,现在我在设计中遇到了一个问题,我似乎无法避免向下转换。我知道这通常是糟糕设计的标志,所以我想知道什么是更好的方法。
我有一个类Frame
,它表示几何框架树,并允许它们之间的几何变换:
class Frame
{
private:
Frame *_parent;
std::vector<Frame*> _children;
public:
Frame* getParent() const;
std::vector<Frame*> getChildren() const;
... (extra methods for geometrical transformations)
}
我现在想创建一个新的Frame
子类MechanicalFrame
,它添加了一些处理动态属性的功能。
class MechanicalFrame
{
private:
double mass;
...
public:
void compute();
}
我的问题是,"计算"方法需要实现一些递归逻辑,所以它会包含这样的东西:
MechanicalFrame::compute()
{
for element in getChildren():
element.compute();
}
然而,由于getChildren
返回Frame*
的vector
,而不是MechanicalFrame*
,因此此时我需要生成static_cast
。我对这个问题想了很多,但没有一个解决方案能让我完全满意:
解决方案1)静态投射:不知何故,它表明设计不好
解决方案2)将计算方法添加到具有伪实现的基类(Frame
)中,即抛出异常:基于派生类强制实现父类似乎不自然。
解决方案3)从Frame
完全拆分MechanicalFrame
:这意味着重新实现Frame
中已经可用的许多功能。
如有任何帮助,我们将不胜感激。非常感谢:)
使用多态行为,使用您的解决方案2)
您可以按照以下模式(接口->基类->派生类)
class IFrame
{
public:
virtual void compute()=0;
}
class Frame:public IFrame
{
public:
virtual void compute() {/*nothing to do*/}
}
class MechanicalFrame:public Frame
{
public:
virtual void compute() {/*your implementation with mass*/}
}
如果您确定MechanicalFrame::getChildren()
中的所有Frame*
指针都指向MechanicalFrame
实例,那么我认为static_cast
没有任何问题。请确保在调试构建中使用dynamic_cast
+assert
来捕获错误。
void MechanicalFrame::compute()
{
for(auto frame_ptr : getChildren())
{
downcast<MechanicalFrame*>(frame_ptr)->compute();
}
}
其中downcast
类似于:
template <typename TOut, typename T>
auto downcast(T* ptr)
{
static_assert(std::is_base_of<T, TOut>{});
assert(ptr != nullptr);
assert(dynamic_cast<TOut>(ptr) == ptr);
return static_cast<TOut>(ptr);
}
(关于downcast
的更彻底的实现,请参阅我的会议C++2015闪电演讲"有意义的演员阵容">或我在vrm_core
中的当前实现。)
请注意,这里有一个性能优势,因为您可以避免virtual
调度。在gcc.godbolt.org上播放这个片段,查看生成的程序集中的差异。
另一个选项是使用访问者模式:
class Frame;
class MechanicalFrame;
class FrameVisitor
{
public:
virtual ~FrameVisitor() = default;
virtual void visit(Frame&) = 0;
virtual void visit(MechanicalFrame&) = 0;
};
class Frame
{
public:
virtual void accept(FrameVisitor& visitor)
{
visitor.visit(*this);
}
void acceptRecursive(FrameVisitor& visitor)
{
accept(visitor);
for (Frame* child : getChildren())
{
child->acceptRecursive(visitor);
}
}
...
};
class MechanicalFrame : public Frame
{
public:
virtual void accept(FrameVisitor& visitor) override
{
visitor.visit(*this);
}
...
};
然后客户端代码将是:
class ConcreteVisitor : public FrameVisitor
{
public:
virtual void visit(Frame& frame) override
{
// Deal with Frame (not a subclass) object.
}
virtual void visit(MechanicalFrame& frame) override
{
// Deal with MechanicalFrame object.
}
};
Frame root = ...;
ConcreteVisitor visitor;
root.acceptRecursive(visitor);
通常,Visitor模式允许您遍历异构对象的层次结构,并在不进行类型转换的情况下对它们执行操作。当类型层次结构或多或少稳定时,操作数量预计会增长时,它最有用。
由于您在寻求新的想法,我不会详细解释您在解决方案1-3中所写的任何内容。
您可以为MechanicalFrame
类添加额外的功能,将其children
从MechanicalFrame
类和所有其他类中分离出来,如下所示:
class Frame {
public:
std::vector<Frame*> getChildren(); // returns children
void addChild(Frame* child); // adds child to children
private:
std::vector<Frame*> children;
}
class MechanicalFrame : public Frame {
public:
void compute();
std::vector<MechanicalFrame*> getMechanicalChildren(); // returns mechanical_children
void addChild(MechanicalFrame* child); // adds child to mechanical_children
private:
std::vector<MechanicalFrame*> mechanical_children;
}
compute
的一种可能实现方式如下:
void MechanicalFrame::compute() {
...
for (auto* child : getMechanicalChildren()) {
child->compute();
}
}
UP:据我所知,强制转换的一个问题是,根据对象的实际类,代码开始表现得非常不同,并且我们不能用子类替换父类对象(请参见Liskov原理)。这个答案中描述的方法实际上改变了使用Frame
的"机械性"的原则,允许以compute
方法中忽略的方式添加MechanicalFrame
子项。
- 继承模板化转换运算符
- 共享指针继承,而不先显式强制转换
- 事件系统:使用类型转换或联合进行继承
- 从继承的类C++强制转换
- 继承会导致不明确的转换
- 将 void* 强制转换为多个继承类的超类不会调用正确的方法
- C++ 关于类继承的隐式转换
- 使用类继承将单个喜欢的列表转换为双向链表
- 如何将C++继承的带有函数的类转换为C样式?
- C++ MSVC 中的访问冲突,但不在 GCC 中进行多重继承和强制转换
- 模板实例和继承的转换错误
- 为什么 GCC 在使用继承的构造函数时警告我无用的强制转换
- 为什么私有继承对象允许成员函数将派生的*转换为基*,而外部不能
- 在多重继承场景中动态强制转换类型
- 使用模板无法按预期工作进行继承和强制转换
- 将void*强制转换为具有多重继承的类
- 避免在继承的树类中进行下转换
- 继承 c++ 向上转换
- 多重继承强制转换未按预期工作
- 继承和隐式类型转换