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 const
ness 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
Such objects can be composed, with Bar
storing std::shared_ptr<const Foo>
members.
可以使用存储std: shared_ptr
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
#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 const
ness 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
Such objects can be composed, with Bar
storing std::shared_ptr<const Foo>
members.
可以使用存储std: shared_ptr
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