Как хранить разные классы в одной переменной?


У меня есть небольшая концептуальная проблема. У меня есть различные классы, представляющие геометрические данные ребра в зависимости от того, какой это тип ребра. Например, класс для прямой линии и круга:

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; Проблема в том, что я не знаю тип до времени выполнения, потому что я анализирую представление границ частей САПР, которые могут имеют различные типы ребер.
class Edge{
private:
    EdgeType type;
    GeometricData data;
public:
    //...
}

Итак, как я должен проектировать мой class Edge и в частности GeometricData , который должен хранить либо Line - Объект, Circle - объект или другой геометрический объект, чтобы я мог вернуться от GeometricData к Line, Circle или какой бы там ни был геометрический класс.

  • я попробовал полиморфизм с GeometricData в качестве базового класса, но производный классы слишком различны, так как такие вещи, как B-сплайны, также являются включенный.
  • я также пробовал GeometricData Как void* и a шаблонный подход для set - и get-methode, но с этим у меня проблемы хранение данных, а не только указателя, из-за срока службы объектов (я должен анализировать BRep рекурсивно).
Я также был бы признателен за предложения, которые могут изменить всю концепцию геометрических представлений, если я могу получить доступ к типовым данным, таким как startPoint прямой линии или radius окружности, используя edges-вектор.

Править: Спасибо за пост. ответы. Я решил использовать предложение suszterpatt, включающее некоторые из моих шаблонов и изменение моего 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;
}
3 2

3 ответа:

Существует ряд решений. Единственное, что кажется наиболее подходящим является наддува.Variant ; определите ваши классы Line и Circle, Как вы показали, затем сделайте GeometricData typedef variant<Line, Circle>, и вы сможете хранить там экземпляр любого из них. Если вы хотите вернуться от GeometricData к сохраненному объекту, вы можете выполнить приведение, или вы можете написать так называемый посетитель. Посетитель-это просто класс, задающий действие для каждого возможного типа, а затем 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.Никаких , но я не вижу никаких преимуществ для этого случая. Даже если вам нужен другой вариант, вы, вероятно, захотите указать его явно.

Я подозреваю, что ваше решение с использованием void* (или с использованием общего базового класса и RTTI) может работать с использованием интеллектуальных указателей. Впрочем, единственный преимущества, которые я вижу, - это более быстрая компиляция и менее ужасные сообщения об ошибках компилятора, в то время как вам в конечном итоге приходится беспокоиться о динамическом выделении и не может иметь посетителей.

Вы также можете свернуть свой собственный союз для этого, эффективно реализуя что-то в духе Variant. Это будет включать в себя обеспечение того, чтобы вы получили построение, разрушение и выравнивание все правильно, и не вызвать какой-то неясный случай неопределенного поведения. Если это не проблема для вас, и вы действительно не хотите использовать библиотеку-это вариант, но он очень сильно изобретает колесо.

Я бы сказал полиморфизм, где, возможно, общий интерфейс выглядит примерно так:

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, но приведенный выше интерфейс дает вам возможность иметь функцию, использующую ребра, содержащие логику для выполнения различных действий, основанных на типе.

Я не уверен, что полностью понимаю проблему, которую вы пытаетесь решить, но из чтения и понимания вопроса я бы сказал, что посмотрите на сериализацию Возможно, вы могли бы создать переменную типа глобального массива, хранить нужные объекты, сериализовать ее и десериализовать, когда вам нужно будет ее использовать.