2013-10-20 83 views
2

我有一個小概念問題。我有不同的類代表邊的幾何數據,具體取決於它的邊緣類型。對於實施例的類的直線和一個圓:如何在一個變量中存儲不同的類?

class Line{ 
private: 
    double[3] startPoint; 
    double[3] endPoint; 
public: 
    //getter and setter and some other functions such as equals 

} 

class Circle{ 
private: 
    double[3] center; 
    double[3] planeNormal; 
    double radius; 
public: 
    //getter and setter and some other functions such as equals 
} 

現在我需要一個類Edge存儲該邊緣和裝配幾何數據的類型。 最後邊緣必須存儲在std::vector<Edge> edges;問題是,我不知道運行時的類型,因爲我正在分析CAD零件的邊界表示,它可以有不同類型的邊緣。

class Edge{ 
private: 
    EdgeType type; 
    GeometricData data; 
public: 
    //... 
} 

所以,我應該如何設計我的class Edge並具有存儲無論是Line -object espacially GeometricData,一個Circle -object或其他幾何對象,這樣我就可以回去從GeometricDataLineCircle或什麼幾何類可能是。

  • 我試圖多態性與GeometricData作爲基類,但衍生 類差異太大,因爲像B樣條曲線也 包括在內。
  • 我也試過GeometricData作爲void*併爲SET-並獲得-梅索德模板的方法 ,但我有,因爲壽命的對象的 (我的問題 存儲數據,而不是隻有指針,必須分析BRep遞歸)。

我也將理解,可以改變幾何表示的整個概念,只要建議,我可以使用edges維矢量訪問類型擬合數據,諸如直線startPoint或圓的radius 。編輯: 感謝您的快速回復。我決定使用suszterpatt的建議,包括我的一些模板,並在提到TAS時將我的std::vector<Edge>更改爲std::vector<shared_ptr<Edge>>。現在看起來像這樣:

#include "stdafx.h" 
#include <string> 
#include <sstream> 
#include <iostream> 
#include <vector> 

using namespace std; 

enum EdgeType{ 
    LINE = 100, 
    CIRCLE 
}; 

//Basis 
class GeometricData { 
private: 
public: 
    virtual string toXMLString() = 0; 
}; 

class Line : public GeometricData{ 
//less code just for illustration 
private: 
    double d1; 
public: 
    double getD1() { return d1; }  
    void setD1(double d1) { this->d1 = d1;} 
    virtual string toXMLString() { 
     stringstream s; 
     s << "d1=\"" << d1 <<"\""; 
     return s.str(); 
    } 
}; 

class Circle : public GeometricData{ 
private: 
    double d2; 
public: 
    double getD2() { return d2; } 
    void setD2(double d2) { this->d2 = d2;} 
    virtual string toXMLString() { 
     stringstream s; 
     s << "d2=\"" << d2<<"\""; 
     return s.str(); 
    } 
}; 

class Edge{ 
private: 
    EdgeType t; 
    GeometricData* d; 
public: 
    Edge() { d = 0;} 
    ~Edge() {if (d) {delete d; d=0;}} 
    template <typename T> int setGeomData (T data) { 
     static_assert(
      is_same<T,Line*>::value || 
      is_same<T,Circle*>::value, 
      "EdgeGeometryType is not supported"); 


     GeometricData* buffer = data; 
      //set type corresponding to thethis->data given= data 

      if(is_same<T,Line*>::value){ 
       this->t = LINE; 
       Line* lb = dynamic_cast<Line*>(buffer); 
       Line* l = new Line(*lb); 
       this->d = l; 
      }else if (is_same<T,Circle*>::value){ 
       this->t = CIRCLE; 
       Circle* cb = dynamic_cast<Circle*>(buffer); 
       Circle* c = new Circle(*cb); 
       this->d = c; 
      }else{// this case should not occure because of the static_assert 
       return -1; 
      } 
      return 0; 

    }; 
    template <typename T> T getGeomData() { 
     static_assert(
      is_same<T,Line*>::value || 
      is_same<T,Circle*>::value, 
      "EdgeGeometryType is not supported"); 

     if ((this->t == LINE  && is_same<T,Line*>::value) || 
      (this->t == CIRCLE  && is_same<T,Circle*>::value)) 
     { 
      return dynamic_cast<T>(this->d); 
     }else{ 
      return NULL; 
     } 
    }; 
    EdgeType getType(){ return t; } 
    //void setType(EdgeType t) { this->t = t; } not needed 
    GeometricData* getData(){return d;} 
}; 

class Model { 
private: 
    vector <shared_ptr<Edge>> edges; 
public: 
    Model(){} 
    vector <shared_ptr<Edge>> getEdges(){ return edges; } 
    void addEdge (Edge* e) {edges.push_back(shared_ptr<Edge>(e));} 
    shared_ptr<Edge> getEdge(int i){ return edges.at(i); } 
}; 

// Functions 
void foo2 (Edge* e){ 
    Line* l = new Line; 
    l->setD1(0.1); 
    e->setGeomData<Line*>(l); 
    //e->setType(LINE); not needed 
    delete l; 
} 
void foo1 (Edge* e){ 
    Circle c; 
    c.setD2(0.2); 
    e->setGeomData<Circle*>(&c); 
    //e->setType(CIRCLE); not needed 
} 
void foo (Model* mdl){ 
    Edge* e1 = new Edge; 
    Edge* e2 = new Edge; 
    foo1(e1); 
    foo2(e2); 
    mdl->addEdge(e1); 
    mdl->addEdge(e2); 
} 
int _tmain(int argc, _TCHAR* argv[]) 
{ 
    Model mdl; 
    int i; 
    foo(&mdl); 
    cout << "Edge 1: " << mdl.getEdge(0)->getData()->toXMLString() << endl; 
    cout << "Edge 2: " << mdl.getEdge(1)->getData()->toXMLString() << endl; 
    for (i = 0; i<2; i++){ 
     switch (mdl.getEdge(i)->getType()){ 
      case LINE: { 
       Line* ld = (mdl.getEdge(i)->getGeomData<Line*>()); 
       cout << "Line (templated get): " << ld->getD1() << endl; 
      }break; 
      case CIRCLE:{ 
       Circle* cr = (mdl.getEdge(i)->getGeomData<Circle*>()); 
       cout << "Circle (templated get): "<< cr->getD2() << endl; 
      }break; 
     } 
    } 
    return 0; 
} 
+0

「我試過多態性,但派生類太不同了」。也許你的基類太具體了?這聽起來像是一個教科書的案例,在這個案例中,多樣性可以挽救一天:各種形狀如何不同? – suszterpatt

+0

嗨suszterpatt, 我是相對多態概念的新手。也許我不正確地理解它。我認爲,如果我想從基礎派生到派生並返回,我需要在基類中派生類的每個方法的虛方法,並且每個派生類中必須實現每個虛方法。 如果我錯了,請糾正我,但是這不代表我的班級'Line'需要一個'getRadius()',因爲這必須是基於'GeometricData'的虛擬方法,因爲'Circle'需要這種方法? –

+0

不是多態類的所有方法都需要是虛擬的。它的方式是,在基類中定義要在所有子類之間共享的常用方法,然後在子類中覆蓋基方法並定義該特定子類所需的其他方法。問題是,你的'Edge'類是否需要知道它正在操作的數據的確切類型?例如。它必須知道一個圓的半徑,或者'GeometricData'中的通用'getArea()'方法是否足夠,'Circle'可以使用該方法並使用其自己的半徑? – suszterpatt

回答

2

有很多解決方案。似乎最適合的那個是Boost.Variant;定義您所示的LineCircle類,然後使GeometricDatavariant<Line, Circle>的typedef,並且您將能夠在其中存儲任一個的實例。當你想從GeometricData回到存儲的實際對象時,你可以perform a cast,或者你可以編寫一個所謂的visitor。訪客只是指定每種可能類型的操作的類,然後可以使用boost::apply_visitor根據所存儲的內容選擇正確的操作。

實施例(使用的載體爲更簡單的表示法):

struct Line { 
    Vector3d startPoint, endPoint; 
}; 

struct Circle { 
    Vector3d center; 
    float radius; 
}; 

using GeometricData = boost::variant<Line, Circle>; 

struct MidpointVisitor : boost::static_visitor<Vector3d> const { 
    Vector3d operator()(Line const& line) { 
     return (line.startPoint + line.endPoint)/2; 
    } 

    Vector3d operator()(Circle const& circle) const { 
     return circle.center; 
    } 
}; 

void foo() { 
    GeometricData data; 
    // ... 
    auto midpoint = boost::apply_visitor(MidpointVisitor{}, data); 
    // ... 
} 

甲較少類型嚴格溶液是Boost.Any,但是我沒有看到任何優點對於這種情況。即使您確實需要其他選項,您可能也想明確指定。

我懷疑你的解決方案使用void*(或使用公共基類和RTTI)可以使用智能指針工作。但是,我能看到的唯一優點是編譯速度更快,編譯器錯誤消息更少,而您最終不得不動輒使用動態分配並且無法訪問。

你也可以推出你自己的聯盟​​,有效地沿着Variant的方式實現一些東西。這將涉及確保你的建設,銷燬和對齊都是正確的,並且不會觸發一些不明確的未定義行爲。如果這對你來說不是問題,而且你真的不想使用庫,那麼這是一個選擇,但它是非常重要的。

+0

感謝您的解釋。這聽起來很像我正在尋找的東西。不幸的是,我從來沒有使用過boost,我的導師也不想在項目中使用另一個庫,所以我必須使用動態分配 –

0

我不知道我完全理解你正在試圖解決這個問題,但閱讀和理解的問題,我想說一下到serialization 也許你可以創建一個全局數組類型變量,存儲你所需要的對象,在需要使用它時序列化它並進行反序列化。

1

我要說的多態性這裏也許共享接口看起來是這樣的:

class Edge 
{ 
    enum EdgeType 
    { 
     CIRCLE, 
     LINE 
    }; 

    EdgeType GetType(); 
} 

然後在switch語句的地方,你可以這樣做:

switch (myEdge.GetType()) 
{ 
    case Edge::EdgeType::CIRCLE: 
     auto myCircle = (Circle)myEdge; 
     // do things specific to circle 
     break; 
    case Edge::EdgeType::LINE: 
     auto myLine = (Line)myEdge; 
     // do things specific to line 
     break; 
} 

話雖這麼說,我會盡可能在switch語句上儘可能多的使用多態,但上面的接口讓你可以選擇使用邊的函數包含基於類型做不同事情的邏輯。

+1

請注意,爲避免在複製std :: vector 時切斷對象,不能用過的。 'vector'必須是某種智能指針或原始指針,fex'std :: vector '。 – TAS

相關問題