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