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

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

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 и особенно GeometricData который должен хранить либо Line-объект, Circle-объект или другой геометрический объект, так что я могу вернуться из GeometricData в Line, Circle или какой-либо геометрический класс это может быть.

  • Я попробовал полиморфизм с GeometricData как базовый класс, но производный
    классы слишком разные, так как такие вещи, как B-сплайны также
    включен.
  • Я тоже пробовал GeometricData как void* и шаблонный подход
    для set- и get-метода, но с этим у меня проблемы
    хранение данных, а не только указатель, из-за времени жизни
    объектов (я должен анализировать 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;
}

2

Решение

Есть ряд решений. Тот, который, кажется, подходит лучше всего, Boost.Variant; определить ваш Line а также Circle классы, как вы показали, а затем сделать GeometricData определение типа 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.Any, но я не вижу никаких преимуществ для этого случая. Даже если вам нужен другой вариант, вы, вероятно, захотите указать это явно.

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

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

2

Другие решения

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

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

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

0