Допустим, у меня есть этот код на Haskell:
data RigidBody = RigidBody Vector3 Vector3 Float Shape -- position, velocity, mass and shape
data Shape = Ball Float -- radius
| ConvexPolygon [Triangle]
Что было бы лучшим способом выразить это в C ++?
struct Rigid_body {
glm::vec3 position;
glm::vec3 velocity;
float mass;
*???* shape;
};
Я спрашиваю, как представить форму внутри структуры, когда она может быть одного из двух типов.
Существуют различные подходы, которые можно использовать для решения этой проблемы в C ++.
Чисто-ОО подход, вы бы определить интерфейс Shape
и иметь две разные опции в качестве производных типов, реализующих этот интерфейс. Тогда RigidBody
будет содержать указатель на Shape
это будет установлено для ссылки либо Ball
или ConvexPolygon
, Pro: люди любят OO (не уверен, что это действительно профессионал :)), он легко расширяемый (вы можете добавить больше фигур позже, не меняя тип). Con: Вы должны определить правильный интерфейс для Shape
требуется динамическое распределение памяти.
Отложив OO в сторону, вы можете использовать boost::variant
или подобный тип, который в основном является теговым объединением, который будет содержать один из типов. Pro: нет динамических распределений, форма локальна для объекта. Con: не чисто OO (люди любят OO, вы помните, верно?), Не так легко расширить, не может использовать форму обобщенно
Чтобы добавить сюда еще одну возможность, вы также можете использовать boost::variant
который добавляется в стандартную библиотеку в C ++ 17 как std::variant
:
struct Ball { float radius; };
struct ConvexPolygon { Triangle t; }
using Shape = boost::variant<Ball, ConvexPolygon>;
Преимущества этого подхода:
Некоторые недостатки:
Канонический способ сделать это в C ++ — это решение на основе наследования, приведенное в ответе Джастина Вуда. Канонически, вы наделяете Shape
с виртуальными функциями, которые каждый вид Shape
Тем не менее, C ++ также имеет union
типы. Вместо этого вы можете сделать «помеченные союзы»:
struct Ball { /* ... */ };
struct Square { /* ... */ };
struct Shape {
int tag;
union {
Ball b;
Square s;
/* ... */
}
};
Вы используете tag
Член сказать, является ли Shape
это Ball
или Square
или что-то еще. Вы можете switch
на tag
член и еще много чего.
Это имеет тот недостаток, что Shape
это один int
больше, чем самый большой из Ball
а также Square
и др; объекты в OCaml и еще много чего не имеют этой проблемы.
Какой метод вы используете, будет зависеть от того, как вы используете Shape
s.
Вы хотите создать базовый класс Shape
, Отсюда вы можете создавать свои классы фигур, Ball
а также ConvexPolygon
, Вы хотите убедиться, что Ball
а также ConvexPolygon
дети базового класса.
class Shape {
// Whatever commonalities you have between the two shapes, could be none.
};
class Ball: public Shape {
// Whatever you need in your Ball class
};
class ConvexPolygon: public Shape {
// Whatever you need in your ConvexPolygon class
};
Теперь вы можете сделать обобщенный объект, как это
struct Rigid_body {
glm::vec3 position;
glm::vec3 velocity;
float mass;
Shape *shape;
};
и когда вы на самом деле инициализировать ваш shape
переменная, вы можете инициализировать его с помощью Ball
или же ConvexPolygon
учебный класс. Вы можете продолжать делать столько фигур, сколько захотите.