声明c++不可变类的惯用方法

时间:2022-07-27 22:02:58

So I have some pretty extensive functional code where the main data type is immutable structs/classes. The way I have been declaring immutability is "practically immutable" by making member variables and any methods const.

所以我有一些非常广泛的函数代码,其中主要的数据类型是不可变结构/类。我声明不变性的方式是“几乎不可变”的,它是通过成员变量和任何方法来实现的。

struct RockSolid {
   const float x;
   const float y;
   float MakeHarderConcrete() const { return x + y; }
}

Is this actually the way "we should do it" in C++? Or is there a better way?

这就是c++中的“我们应该这么做”吗?或者有更好的方法吗?

2 个解决方案

#1


30  

The way you proposed is perfectly fine, except if in your code you need to make assignment of RockSolid variables, like this:

你提出的方法是完全正确的,除非在你的代码中你需要分配RockSolid变量,比如:

RockSolid a(0,1);
RockSolid b(0,1);
a = b;

This would not work as the copy assignment operator would have been deleted by the compiler.

这将不起作用,因为复制分配操作符将被编译器删除。

So an alternative is to rewrite your struct as a class with private data members, and only public const functions.

因此,另一种方法是将您的结构体重写为一个具有私有数据成员和仅具有公共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; }
 }

In this way, your RockSolid objects are (pseudo-)immutables, but you are still able to make assignments.

这样,您的RockSolid对象是(pseudo-)immutables,但您仍然能够完成任务。

#2


9  

I assume your goal is true immutability -- each object, when constructed, cannot be modified. You cannot assign one object over another.

我假设你的目标是真正的不变性——每一个物体,一旦构造,就不能被修改。不能将一个对象分配给另一个对象。

The biggest downside to your design is that it is not compatible with move semantics, which can make functions returning such objects more practical.

设计的最大缺点是它与move语义不兼容,这使得返回这些对象的函数更加实用。

As an example:

作为一个例子:

struct RockSolidLayers {
  const std::vector<RockSolid> layers;
};

we can create one of these, but if we have a function to create it:

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

RockSolidLayers make_layers();

it must (logically) copy its contents out to the return value, or use return {} syntax to directly construct it. Outside, you either have to do:

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

RockSolidLayers&& layers = make_layers();

or again (logically) copy-construct. The inability to move-construct will get in the way of a number of simple ways to have optimal code.

或者再次copy-construct(逻辑上)。不能移动-构造将会阻碍许多简单的方法来获得最优代码。

Now, both of those copy-constructions are elided, but the more general case holds -- you cannot move your data from one named object to another, as C++ does not have a "destroy and move" operation that both takes a variable out of scope and uses it to construct something else.

现在,这两种复制结构都被省略了,但更一般的情况是——您不能将数据从一个命名对象移动到另一个命名对象,因为c++没有“销毁和移动”操作,既可以将变量从作用域中取出,又可以使用它来构造其他东西。

And the cases where C++ will implicitly move your object (return local_variable; for example) prior to destruction are blocked by your const data members.

而c++将隐式移动对象的情况(返回local_variable;例如)销毁前被const数据成员阻塞。

In a language designed around immutable data, it would know it can "move" your data despite its (logical) immutability.

在一种围绕不可变数据设计的语言中,它知道它可以“移动”你的数据,尽管它(逻辑)不变性。

One way to go about this problem is to use the heap, and store your data in std::shared_ptr<const Foo>. Now the constness is not in the member data, but rather in the variable. You can also only expose factory functions for each of your types that returns the above shared_ptr<const Foo>, blocking other construction.

解决这个问题的一种方法是使用堆,并将数据存储在std: shared_ptr 中。现在,一致性不在成员数据中,而在变量中。您还可以仅为返回上述shared_ptr 的每个类型公开工厂函数,从而阻塞其他构造。

Such objects can be composed, with Bar storing std::shared_ptr<const Foo> members.

可以使用存储std: shared_ptr 成员的Bar来组合这些对象。

A function returning a std::shared_ptr<const X> can efficiently move the data, and a local variable can have its state moved into another function once you are done with it without being able to mess with "real" data.

返回std: shared_ptr 的函数可以有效地移动数据,而本地变量可以在您使用它之后将其状态移动到另一个函数中,而不影响“真实”数据。

For a related technique, it is idomatic in less constrained C++ to take such shared_ptr<const X> and store them within a wrapper type that pretends they are not immutable. When you do a mutating operaiton, the shared_ptr<const X> is cloned and modified, then stored. An optimization "knows" that the shared_ptr<const X> is "really" a shared_ptr<X> (note: make sure factory functions return a shared_ptr<X> cast to a shared_ptr<const X> or this is not actually true), and when the use_count() is 1 instead casts away const and modifies it directly. This is an implementation of the technique known as "copy on write".

对于相关的技术,使用这样的shared_ptr 并将它们存储在一个包装器类型中,该包装器类型假装它们不是不可变的。当您执行一个突变操作数时,shared_ptr 被克隆和修改,然后被存储。优化“知道”shared_ptr “确实”是shared_ptr (注意:确保工厂函数返回shared_ptr ),并将其强制转换为shared_ptr ,或者这实际上不是正确的),当use_count()直接转换为1时。这是“写时复制”技术的实现。

#1


30  

The way you proposed is perfectly fine, except if in your code you need to make assignment of RockSolid variables, like this:

你提出的方法是完全正确的,除非在你的代码中你需要分配RockSolid变量,比如:

RockSolid a(0,1);
RockSolid b(0,1);
a = b;

This would not work as the copy assignment operator would have been deleted by the compiler.

这将不起作用,因为复制分配操作符将被编译器删除。

So an alternative is to rewrite your struct as a class with private data members, and only public const functions.

因此,另一种方法是将您的结构体重写为一个具有私有数据成员和仅具有公共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; }
 }

In this way, your RockSolid objects are (pseudo-)immutables, but you are still able to make assignments.

这样,您的RockSolid对象是(pseudo-)immutables,但您仍然能够完成任务。

#2


9  

I assume your goal is true immutability -- each object, when constructed, cannot be modified. You cannot assign one object over another.

我假设你的目标是真正的不变性——每一个物体,一旦构造,就不能被修改。不能将一个对象分配给另一个对象。

The biggest downside to your design is that it is not compatible with move semantics, which can make functions returning such objects more practical.

设计的最大缺点是它与move语义不兼容,这使得返回这些对象的函数更加实用。

As an example:

作为一个例子:

struct RockSolidLayers {
  const std::vector<RockSolid> layers;
};

we can create one of these, but if we have a function to create it:

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

RockSolidLayers make_layers();

it must (logically) copy its contents out to the return value, or use return {} syntax to directly construct it. Outside, you either have to do:

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

RockSolidLayers&& layers = make_layers();

or again (logically) copy-construct. The inability to move-construct will get in the way of a number of simple ways to have optimal code.

或者再次copy-construct(逻辑上)。不能移动-构造将会阻碍许多简单的方法来获得最优代码。

Now, both of those copy-constructions are elided, but the more general case holds -- you cannot move your data from one named object to another, as C++ does not have a "destroy and move" operation that both takes a variable out of scope and uses it to construct something else.

现在,这两种复制结构都被省略了,但更一般的情况是——您不能将数据从一个命名对象移动到另一个命名对象,因为c++没有“销毁和移动”操作,既可以将变量从作用域中取出,又可以使用它来构造其他东西。

And the cases where C++ will implicitly move your object (return local_variable; for example) prior to destruction are blocked by your const data members.

而c++将隐式移动对象的情况(返回local_variable;例如)销毁前被const数据成员阻塞。

In a language designed around immutable data, it would know it can "move" your data despite its (logical) immutability.

在一种围绕不可变数据设计的语言中,它知道它可以“移动”你的数据,尽管它(逻辑)不变性。

One way to go about this problem is to use the heap, and store your data in std::shared_ptr<const Foo>. Now the constness is not in the member data, but rather in the variable. You can also only expose factory functions for each of your types that returns the above shared_ptr<const Foo>, blocking other construction.

解决这个问题的一种方法是使用堆,并将数据存储在std: shared_ptr 中。现在,一致性不在成员数据中,而在变量中。您还可以仅为返回上述shared_ptr 的每个类型公开工厂函数,从而阻塞其他构造。

Such objects can be composed, with Bar storing std::shared_ptr<const Foo> members.

可以使用存储std: shared_ptr 成员的Bar来组合这些对象。

A function returning a std::shared_ptr<const X> can efficiently move the data, and a local variable can have its state moved into another function once you are done with it without being able to mess with "real" data.

返回std: shared_ptr 的函数可以有效地移动数据,而本地变量可以在您使用它之后将其状态移动到另一个函数中,而不影响“真实”数据。

For a related technique, it is idomatic in less constrained C++ to take such shared_ptr<const X> and store them within a wrapper type that pretends they are not immutable. When you do a mutating operaiton, the shared_ptr<const X> is cloned and modified, then stored. An optimization "knows" that the shared_ptr<const X> is "really" a shared_ptr<X> (note: make sure factory functions return a shared_ptr<X> cast to a shared_ptr<const X> or this is not actually true), and when the use_count() is 1 instead casts away const and modifies it directly. This is an implementation of the technique known as "copy on write".

对于相关的技术,使用这样的shared_ptr 并将它们存储在一个包装器类型中,该包装器类型假装它们不是不可变的。当您执行一个突变操作数时,shared_ptr 被克隆和修改,然后被存储。优化“知道”shared_ptr “确实”是shared_ptr (注意:确保工厂函数返回shared_ptr ),并将其强制转换为shared_ptr ,或者这实际上不是正确的),当use_count()直接转换为1时。这是“写时复制”技术的实现。