避免在继承的树类中进行向下转换

时间:2017-01-16 09:59:12

标签: c++

我对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();
 }

我的问题是,“compute”方法需要实现一些递归逻辑,所以它包含这样的东西:

MechanicalFrame::compute()
{
   for element in getChildren():
     element.compute();
}

但是,由于getChildren返回的vector Frame*而非MechanicalFrame*,我需要在此时生成static_cast。我已经把这个问题考虑了很多,但我发现的解决方案都没有让我完全满意:

解决方案1)静态演员:不知何故,它表明设计不好

解决方案2)使用虚拟实现将计算方法添加到基类(Frame),即抛出异常:基于派生类强制实现父类似乎不自然。 / p>

解决方案3)完全从MechanicalFrame拆分Frame:这意味着重新实现Frame中已有的许多功能。

非常感谢任何帮助。 非常感谢提前:))

4 个答案:

答案 0 :(得分:2)

使用多态行为,使用您的解决方案2)

您可以按照以下模式(界面 - &gt;基类 - &gt;派生类)

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*/}
}

答案 1 :(得分:1)

如果您确定Frame*中的所有MechanicalFrame::getChildren()指针都指向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的更全面实施,请参阅我的Meeting C++ 2015 lightning talk "Meaningful casts"或我的current implementation in vrm_core。)

请注意,这里有一个性能优势,因为您可以避免virtual调度。到处使用this snippet on gcc.godbolt.org来查看生成的程序集中的差异。

答案 2 :(得分:0)

另一种选择是使用访客模式:

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

通常,访问者模式允许您遍历异构对象的层次结构并对其执行操作而无需进行类型转换。当您的类型层次结构或多或少稳定时,预计操作数量会增长时最有用。

答案 3 :(得分:-1)

由于您要求新的想法,我不会详细解释您在解决方案1-3中所写的任何内容。

您可以为async类添加额外的功能,将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

UP:据我了解,强制转换的一个问题是代码的开始行为取决于对象的实际类,我们不能用父类对象替换儿童班(见Liskov principle)。本回答中描述的方法实际上改变了使用void MechanicalFrame::compute() { ... for (auto* child : getMechanicalChildren()) { child->compute(); } } 的“机制”的原则,允许添加Frame个孩子,使他们在MechanicalFrame方法中被忽略。