Question

I have a little conceptual problem. I have different classes representing the geometric data of an edge depending what type of edge it is. For Example the class for a straight line and a circle:

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   
}

Now I need a class Edge which stores the type of the edge and the fitting geometric data. In the end the Edge has to be stored in a std::vector<Edge> edges; The Problem is that I do not know the type before runtime, because I am analysing the boundary representation of CAD parts which can have various types of edges.

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

So how should I design my class Edge and espacially GeometricData which has to store either a Line-object, a Circle-object or another geometric object, so that I can go back from GeometricData to Line, Circle or whatever geometric class it may be.

  • I tried polymorphism with GeometricData as base class, but the derived classes are too different, since things like B-Splines are also included.
  • I also tried GeometricData as void* and a template-approach for the set- and get-methode, but with that I have problems storing the data and not only the pointer, because of the lifetime of the objects (I have to analyse the BRep recursivly).

I would also appreciate suggestions that may change the whole concept of the geometric representations, as long as I can access the type-fitting data such as startPoint of a straight line or radius of a circle using the edges-vector.

EDIT: Thanks for the fast responses. I decided to use suszterpatt suggestion including some of my templates and changing my std::vector<Edge> to std::vector<shared_ptr<Edge>> as TAS mentioned. Now it looks like this:

#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;
}
Was it helpful?

Solution

There's a number of solutions. The one that seems to fit best is Boost.Variant; define your Line and Circle classes as you showed, then make GeometricData a typedef of variant<Line, Circle>, and you'll be able to store an instance of either one in there. When you want to go back from a GeometricData to the actual object stored, you can perform a cast, or you can write a so-called visitor. A visitor is just a class specifying an action for each possible type, and then boost::apply_visitor can be used to select the right action based on what is stored.

Example (using vectors for simpler notation):

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

A less type-strict solution is Boost.Any, but I don't see any advantages for this case. Even if you did need another option, you'd probably want to specify that explicitly.

I suspect your solution using void* (or using a common base class and RTTI) could be made to work using smart pointers. However, the only advantages I can see are faster compilation and less awful compiler error messages, while you end up having to bother with dynamic allocation and can't have visitors.

You could also roll your own union for this, effectively implementing something along the lines of Variant. That would involve making sure you get construction, destruction and alignment all correct, and don't trigger some obscure case of undefined behaviour. If that's not a problem for you and you really don't want to use a library, it's an option, but it is very much reinventing the wheel.

OTHER TIPS

I would say polymorphism where perhaps the shared interface looks something like this:

class Edge
{
    enum EdgeType
    {
        CIRCLE,
        LINE
    };

    EdgeType GetType();
}

Then in a switch statement somewhere you could do something like:

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

That being said, I would try to use polymorphism as much as possible over the switch statement, but the above interface gives you the option of having a function using edges contain the logic for doing different things based on type.

I'm not sure I fully understand the problem you're trying solve but from reading and understanding the question, I'd say look into serialization You could maybe create a global array type variable, store the objects you need, serialize it an deserialize it when you need to use it.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top