2017-01-16 8 views
1

私は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(); 
} 

私の問題は、「コンピューティング」の方法は、いくつかの再帰的なロジックを実装する必要があります、ということですので、このような何か含まれています:getChildrenFrame*とないのvectorを返すために、しかし

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

MechanicalFrame*、この時点でstatic_castを作成する必要があります。私はこの問題に多くの考えを与えてくれたが、私が見つけた解決策のどれも私には完全に満足されない:

ソリューション1)静的キャスト:何とかそれは悪いデザインが

解決法2)の追加を示しています基本クラス(Frame)にメソッドを実装して、例外をスローします。派生クラスに基づいて親クラスの実装を強制するのは不自然です。

解決策3)Frameから完全にMechanicalFrameに分割する:これは既にFrameで利用可能な多くの機能を再実装することを意味します。

ご協力いただければ幸いです。あなたがMechanicalFrame::getChildren()内のすべてのFrame*ポインタがMechanicalFrameのインスタンスを指していることが確実な場合は事前に 感謝:)

+0

基本クラスに 'Frame :: compute()'を作成し、 'virtual'にします。問題は、基本クラスをインスタンス化する予定ですか? 'Frame'を抽象クラスにすることは良い考えです。 – pSoLT

+0

'MechanicalFrame :: getChildren()'の 'Frame *'ポインタが 'MechanicalFrame'インスタンスを指していることが確かであれば、' static_cast'には何の問題もありません。デバッグビルド**で 'dynamic_cast' +' assert'を使って間違いをキャッチするようにしてください。 –

+0

@VittorioRomeo本当に醜いデザインです。 – pSoLT

答えて

1

は、私がstatic_castに問題が表示されません。間違いをキャッチするには、の + assertでビルドしてください。 downcastのようなものである

void MechanicalFrame::compute() 
{ 
    for(auto frame_ptr : getChildren()) 
    { 
     downcast<MechanicalFrame*>(frame_ptr)->compute(); 
    } 
} 

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ディスパッチch。 this snippet on gcc.godbolt.orgで遊んで、生成されたアセンブリの違いを確認してください。

2

使用多型の行動、あなたのソリューション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*/} 
} 
+1

これは私がコメントで書いたものです。 – pSoLT

-1

- >基底クラスあなたが解決策1〜3で書いたことを詳しく説明しません。

あなたはそのchildrenMechanicalFrameのクラスを分割し、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の1つの可能な実装は、次のとおりです。

void MechanicalFrame::compute() { 
    ... 
    for (auto* child : getMechanicalChildren()) { 
     child->compute(); 
    } 
} 

UP:私が理解する限り、キャストの問題の1つは、コードが非常に異なる依存関係を始めることですオブジェクトの実際のクラスではなく、親クラスオブジェクトを子クラスに置き換えることはできません(Liskov principleを参照)。この回答に記載されているアプローチは、実際にFrameの「機械的性」を使用するという原則を変更し、MechanicalFrame子を追加して、computeメソッドでは無視されるようにします。そして、クライアントコードがなる

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

    ... 
}; 

0

別のオプションは、Visitorパターンを使用することで、一般的に

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パターンを使用すると、異種のオブジェクトの階層をトラバースすることを可能にし、型キャストなしでそれらに対して操作を実行します。これは、タイプ階層がより安定している間に操作数が増加すると予想される場合に最も役立ちます。

関連する問題