C++等效于代数数据类型

C++ equivalent of algebraic datatype?

本文关键字:代数数据类型 C++      更新时间:2023-10-16

假设我有这个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++中可以使用不同的方法来解决该问题。

纯 OO 方法Shape定义接口,并将两个不同的选项作为实现该接口的派生类型。然后,RigidBody将包含一个指向Shape的指针,该指针将设置为引用BallConvexPolygon。优点:人们喜欢OO(不确定这是否真的:)),它很容易扩展(您可以稍后添加更多形状而无需更改类型)。缺点:你应该为Shape定义一个合适的接口,它需要动态分配内存。

撇开OO不谈,您可以使用boost::variant或类似的类型,这基本上是一个标记的联合,将保存其中一种类型。优点:没有动态分配,形状是对象的局部。缺点:不是纯OO(人们喜欢OO,你还记得吧?),不太容易扩展,不能一般使用形状

要在这里抛出另一种可能性,您还可以使用 C++17 中添加到标准库中的boost::variant作为std::variant

struct Ball { float radius; };
struct ConvexPolygon { Triangle t; }
using Shape = boost::variant<Ball, ConvexPolygon>;

这种方法的优点:

  • 类型安全,与标记联合不同
  • 可以容纳复杂类型,与联合不同
  • 不需要跨所有"子"类型的统一接口,与 OO 不同

一些缺点:

  • 有时要求您在访问变量时进行类型检查,以确认它是您想要的类型,这与 OO 不同
  • 要求您使用 boost 或与 C++17 兼容;对于某些编译器或某些普遍支持 OO 和联合的组织来说,这些可能很困难

在C++中做到这一点的规范方法是贾斯汀伍德的答案中给出的基于继承的解决方案。 通常,您赋予Shape每种Shape的虚拟函数

但是,C++也有union类型。 您可以改为执行"标记联合":

struct Ball { /* ... */ };
struct Square { /* ... */ };
struct Shape {
  int tag;
  union {
    Ball b;
    Square s;
    /* ... */
  }
};

您可以使用tag成员来说明ShapeBall还是Square或其他任何东西。 您可以switch tag成员之类的。

这样做的缺点是,ShapeBallSquare中最大的int大;OCaml中的对象等等都没有这个问题。

您使用哪种技术将取决于您如何使用Shape

您将要创建一个基类Shape。从这里,您可以创建实际的形状类、BallConvexPolygon。您需要确保BallConvexPolygon是基类的子类。

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变量时,您可以使用 BallConvexPolygon 类对其进行初始化。您可以继续制作任意数量的形状。