惯用法声明C++不可变类

Bla*_*iwi 34 c++ functional-programming const-correctness immutability c++11

所以我有一些相当广泛的功能代码,主要数据类型是不可变的结构/类.通过制作成员变量和任何方法const,我一直在声明不变性的方式是"几乎不可变的".

struct RockSolid {
   const float x;
   const float y;
   float MakeHarderConcrete() const { return x + y; }
}
Run Code Online (Sandbox Code Playgroud)

这实际上是C++中"我们应该这样做"的方式吗?或者,还有更好的方法?

chr*_*phb 33

你提出的方式非常好,除非在你的代码中你需要分配RockSolid变量,如下所示:

RockSolid a(0,1);
RockSolid b(0,1);
a = b;
Run Code Online (Sandbox Code Playgroud)

这不起作用,因为编译器已删除了复制赋值运算符.

因此,另一种方法是将结构重写为具有私有数据成员的类,并且只重写公共const函数.

class RockSolid {
  private:
    float x;
    float y;

  public:
    RockSolid(float _x, float _y) : x(_x), y(_y) {
    }
    float MakeHarderConcrete() const { return x + y; }
    float getX() const { return x; }
    float getY() const { return y; }
 }
Run Code Online (Sandbox Code Playgroud)

通过这种方式,您的RockSolid对象是(伪)不可变的,但您仍然可以进行分配.

  • @MartinMoene:"idiom"仍构建非可复制赋值和不可移动赋值的值对象(或依赖于堆).oikosdev的解决方案没有遭受这个缺点,并且不需要(伴侣)样板.它只是工作......为什么一切复杂化? (3认同)
  • 至少对于这个项目,我特别想要的语义与Haskell中的"900磅功能语言"相同,其中必须明确(类型)构造任何新状态. (2认同)
  • 您可以考虑使用[immutable value-mutable companion idiom](http://martin-moene.blogspot.com/2012/08/growing-immutable-value.html). (2认同)
  • “struct”和“class”是同一件事,但其中一个默认为“public:”,另一个默认为“private:”。建议使用“class”并没有什么区别。其他语言,如 C# 或 D,存在语义差异。 (2认同)

Yak*_*ont 10

我假设你的目标是真正的不变性 - 每个对象在构造时都无法修改.您不能将一个对象分配给另一个对象.

您的设计最大的缺点是它与移动语义不兼容,移动语义可以使返回此类对象的函数更加实用.

举个例子:

struct RockSolidLayers {
  const std::vector<RockSolid> layers;
};
Run Code Online (Sandbox Code Playgroud)

我们可以创建其中一个,但是如果我们有一个函数来创建它:

RockSolidLayers make_layers();
Run Code Online (Sandbox Code Playgroud)

它必须(逻辑上)将其内容复制到返回值,或使用return {}语法直接构造它.外面,你要么做:

RockSolidLayers&& layers = make_layers();
Run Code Online (Sandbox Code Playgroud)

或者(逻辑上)复制构造.无法移动构造将妨碍许多简单的方法来获得最佳代码.

现在,两个复制结构都被省略了,但更一般的情况仍然存在 - 你不能数据从一个命名对象移动到另一个命名对象,因为C++没有"销毁和移动"操作,它们都接受一个变量范围并使用它来构造其他东西.

而且,在return local_variable;破坏之前C++将隐式移动您的对象(例如)的情况会被您的const数据成员阻止.

在围绕不可变数据设计的语言中,它会知道它可以"移动"您的数据,尽管它具有(逻辑)不变性.

解决此问题的一种方法是使用堆,并将数据存储在其中std::shared_ptr<const Foo>.现在,constness不在成员数据中,而是在变量中.您也可以只为每个返回上述类型的类型公开工厂函数shared_ptr<const Foo>,从而阻止其他构造.

这些对象可以与Bar存储std::shared_ptr<const Foo>成员一起组成.

返回a的函数std::shared_ptr<const X>可以有效地移动数据,并且局部变量可以在完成后将其状态移动到另一个函数中,而不会弄乱"真实"数据.

对于相关技术,在较少约束的C++中采用这种方法shared_ptr<const X>并将其存储在假装它们不是不可变的包装类型中是惰性的.当你进行变异操作时,shared_ptr<const X>克隆并修改,然后存储.优化"知道"它shared_ptr<const X>是"真的"a shared_ptr<X>(注意:确保工厂函数返回一个强制转换shared_ptr<X>为a shared_ptr<const X>或者这实际上不是真的),并且当use_count()为1 时抛弃const并直接修改它.这是称为"写入时复制"的技术的实现.