所以我有一些相当广泛的功能代码,其中主数据类型是不可变的结构/类。通过制作成员变量和任何方法const,我一直在声明不变性的方式是“几乎不可变的”。
struct RockSolid {
const float x;
const float y;
float MakeHarderConcrete() const { return x + y; }
}
这实际上是C ++中“我们应该这样做”的方式吗?或者有更好的方法吗?
答案 0 :(得分:31)
你建议的方式非常好,除非在你的代码中你需要分配RockSolid变量,如下所示:
RockSolid a(0,1);
RockSolid b(0,1);
a = b;
这不起作用,因为编译器会删除复制赋值运算符。
所以另一种方法是将结构重写为具有私有数据成员的类,并且只重写公共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; }
}
通过这种方式,您的RockSolid对象是(伪)不可变的,但您仍然可以进行分配。
答案 1 :(得分:9)
我认为你的目标是真正的不变性 - 每个对象在构造时都无法修改。您不能将一个对象分配给另一个对象。
您设计的最大缺点是它与移动语义不兼容,移动语义可以使返回此类对象的函数更加实用。
举个例子:
struct RockSolidLayers {
const std::vector<RockSolid> layers;
};
我们可以创建其中一个,但是如果我们有一个函数来创建它:
RockSolidLayers make_layers();
它必须(逻辑上)将其内容复制到返回值,或使用return {}
语法直接构造它。外面,你要么做:
RockSolidLayers&& layers = make_layers();
或再次(逻辑上)复制构造。无法移动构造将妨碍许多简单的方法来获得最佳代码。
现在,两个复制结构都被省略了,但更一般的情况仍然存在 - 你不能将数据从一个命名对象移动到另一个命名对象,因为C ++没有&#34 ;摧毁和移动&#34;这两个操作都使变量超出范围并使用它来构造其他东西。
在您的return local_variable;
数据成员阻止C ++在销毁之前隐式移动您的对象(例如const
)的情况。
在围绕不可变数据设计的语言中,它会知道它可以移动&#34;您的数据尽管具有(逻辑)不变性。
解决此问题的一种方法是使用堆,并将数据存储在std::shared_ptr<const Foo>
中。现在const
不在成员数据中,而是在变量中。您还可以仅为返回上述shared_ptr<const Foo>
的每种类型公开工厂函数,从而阻止其他构造。
这些对象可以由Bar
存储std::shared_ptr<const Foo>
成员组成。
返回std::shared_ptr<const X>
的函数可以有效地移动数据,并且局部变量一旦完成就可以将其状态移动到另一个函数中而不会弄乱&#34;真正的&#34;数据
对于相关技术,在受约束较少的C ++中采用这样的shared_ptr<const X>
并将它们存储在假装它们不是不可变的包装类型中是偶像的。当你进行变异操作时,shared_ptr<const X>
被克隆并修改,然后存储。优化&#34;知道&#34; shared_ptr<const X>
是&#34;真的&#34; a shared_ptr<X>
(注意:确保工厂函数返回shared_ptr<X>
强制转换为shared_ptr<const X>
或者这实际上不是真的),当use_count()
为1而不是强制转换{ {1}}并直接修改它。这是一种称为&#34; copy on write&#34;。