使用const公共成员强制所有实例的不变性是合理的做法吗

Is it reasonable practice to enforce the immutability of all instances using const public members?

本文关键字:不变性 实例 const 成员 使用      更新时间:2023-10-16

我有一个类,它基本上是一堆字段的结构(比如说,类似于二维点的东西;但它只是这个问题的合成示例),我只想要不可变的实例。显然,我可以使数据成员私有,并且只有getter方法:

class Point2d {
protected:
   double x;
   double y;
public:
   Point2d(double _x, double _y) : x(_x), y(_y) {}
   double getX() const { return x; }
   double getY() const { return y; }
   double norm() const { return x*x + y*y; }
}

但是,我在想也许这样做更好:

class Point2d {
public:
   const double x;
   const double y;
public:
   Point2d(double _x, double _y) : x(_x), y(_y) {}
   double norm() const { return x*x + y*y; }
   Point2d operator=(const Point2d& p) = delete;
}

编辑:第三种选择是在单个变量的声明中仅使用constness来强制不变性,类本身是可变的,即

class Point2d {
public:
   double x;
   double y;
public:
   Point2d(double _x, double _y) : x(_x), y(_y) {}
   double norm() const { return x*x + y*y; }
}
// ...
Point2d magicalTransformation(const Point2d& p1, const Point2d& p2);

加强不变性的更合理的方法是什么?

在设计一个类时,灵活性和保证之间总是存在张力。强制执行数据成员的常量(排除了赋值)会产生一个非常严格的类:这有成本(灵活性的损失),但收益是什么?

当然,任何希望冻结实例的用户都可以使用const来执行此操作。所以你没有给用户带来任何"新"功能,但你剥夺了他们的灵活性:这值得吗?

我认为你太极端了,因为赋值不起作用,你可能会发现自己在最常见的操作中使用类的实例时遇到了困难(你可以制作一个数组,但以后不能对数组进行排序)。

因此,我建议您简单地防止在分配之外对进行任何修改。这足以毫不费力地维护类不变量。

这是否合理?

是的。在我看来,这是一种更好的风格,因为它提高了可读性
总有一天,比起getter方法,我更喜欢const(不可变)public成员。实际上,如果以正确的方式声明,每个数据类型都会发送特定的消息。例如:

  • const数据===>成员的寿命
  • public=>在class之外读取成员
  • protected成员===>在继承中读取该成员世袭制
  • private成员===>该成员仅在class范围内读取

所以这里的意图是明确的。一旦确定成员const在其生存期内不会更改,就可以声明该成员。应该根据潜在getter方法的使用位置来选择访问说明符。

此外,我真的需要使用const public成员?

是的。这是最好的,因为您需要const对象或其他const方法中的const正确版本。

编辑
关于修改后的问题。一旦你在class中有一个const成员,operator =就会被隐式删除,所以你不必= delete。这只是品味的问题。对我来说,它变得多余了
事实上,在g++编译器中,如果你试图使用赋值修改这样的对象,编译器会自己生成一个错误,比如:

error: use of deleted function ‘Point2D& Point2D::operator=(const Point2D&)’

另一条语句是函数声明:

const Point2d findMagicPoint();

返回类型不需要是const,因为它无论如何都是不可修改的右值。例如CCD_ 21是不成形的。

两者都能工作。

两者之间很可能没有性能差异,因为getX()getY()被优化为"零"。[您可能想在GetY tho'中使用return y;,这可能有助于解决一两个错误]。

这主要是一个风格问题。

当然,你可能也会有这个类的non-const版本,当你想在使用它之前对点进行计算时。

我不确定是否可以为这个问题提供一个事实,但在我看来,对像Point2D这样的小对象使用get和set是多余的。如果要将这些对象用作常量,只需将它们设为常量即可。如果要将它们用作变量,那么只需重载运算符(如"+"、"+="等)并使用它们。应该封装作为数据成员的Point2D,而不是Point2D的数据成员。同样,如果要以不可变的方式使用它们,只需使用const Point2D即可。以后您可能需要更改(以可变方式使用)Point2D的数据。常量数据成员对此类没有任何作用。