haskell — эквивалент C ++ алгебраического типа данных?

Допустим, у меня есть этот код на 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;
};

Я спрашиваю, как представить форму внутри структуры, когда она может быть одного из двух типов.

11

Решение

Существуют различные подходы, которые можно использовать для решения этой проблемы в C ++.

Чисто-ОО подход, вы бы определить интерфейс Shape и иметь две разные опции в качестве производных типов, реализующих этот интерфейс. Тогда RigidBody будет содержать указатель на Shape это будет установлено для ссылки либо Ball или ConvexPolygon, Pro: люди любят OO (не уверен, что это действительно профессионал :)), он легко расширяемый (вы можете добавить больше фигур позже, не меняя тип). Con: Вы должны определить правильный интерфейс для Shapeтребуется динамическое распределение памяти.

Отложив OO в сторону, вы можете использовать boost::variant или подобный тип, который в основном является теговым объединением, который будет содержать один из типов. Pro: нет динамических распределений, форма локальна для объекта. Con: не чисто OO (люди любят OO, вы помните, верно?), Не так легко расширить, не может использовать форму обобщенно

10

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

Чтобы добавить сюда еще одну возможность, вы также можете использовать boost::variant который добавляется в стандартную библиотеку в C ++ 17 как std::variant:

struct Ball { float radius; };
struct ConvexPolygon { Triangle t; }

using Shape = boost::variant<Ball, ConvexPolygon>;

Преимущества этого подхода:

  • Тип безопасности, в отличие от теговых союзов
  • Может содержать сложные типы, в отличие от профсоюзов
  • Не требует единого интерфейса для всех «дочерних» типов, в отличие от ОО

Некоторые недостатки:

  • Иногда требуется, чтобы вы обращались к переменной при проверке типа, чтобы убедиться, что это именно тот тип, который вам нужен, в отличие от OO
  • Требует, чтобы вы использовали boost или были совместимы с C ++ 17; это может быть трудно с некоторыми компиляторами или некоторыми организациями, где OO и союзы поддерживаются повсеместно
5

Канонический способ сделать это в 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 и еще много чего не имеют этой проблемы.

Какой метод вы используете, будет зависеть от того, как вы используете Shapes.

2

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

0