我假设您的目标是真正的不变性——每个对象在构造时都不能被修改。您不能将一个对象分配给另一个对象。
您设计的最大缺点是它与移动语义不兼容,这会使返回此类对象的函数更加实用。
举个例子:
struct RockSolidLayers {
const std::vector<RockSolid> layers;
};
我们可以创建其中之一,但如果我们有创建它的函数:
RockSolidLayers make_layers();
它必须(逻辑上)将其内容复制到返回值,或者使用return {} 语法直接构造它。在外面,你要么必须做:
RockSolidLayers&& layers = make_layers();
或再次(逻辑上)复制构造。无法移动构造会妨碍许多简单的方法来获得最佳代码。
现在,这两个复制构造都被省略了,但更一般的情况是——您不能将数据从一个命名对象移动到另一个,因为 C++ 没有“销毁和move" 操作,既可以将变量带出范围,也可以使用它来构造其他内容。
而在销毁之前 C++ 会隐式移动您的对象(例如 return local_variable;)的情况会被您的 const 数据成员阻止。
在围绕不可变数据设计的语言中,它会知道它可以“移动”您的数据,尽管其(逻辑)不可变。
解决此问题的一种方法是使用堆,并将数据存储在std::shared_ptr<const Foo> 中。现在constness 不在成员数据中,而是在变量中。您还可以只为返回上述shared_ptr<const Foo> 的每种类型公开工厂函数,从而阻止其他构造。
此类对象可以组合,Bar 存储 std::shared_ptr<const Foo> 成员。
返回std::shared_ptr<const X> 的函数可以有效地移动数据,而局部变量可以在完成后将其状态转移到另一个函数中,而不会弄乱“真实”数据。
对于一项相关技术,在约束较少的 C++ 中采用这种shared_ptr<const X> 并将它们存储在假装它们不是不可变的包装类型中是惯用的。当您执行变异操作时,shared_ptr<const X> 被克隆和修改,然后存储。优化“知道”shared_ptr<const X>“真的”是shared_ptr<X>(注意:确保工厂函数将shared_ptr<X> 转换为shared_ptr<const X>,否则这实际上不是真的),并且当use_count() is 1 而是丢弃const 并直接修改它。这是一种被称为“写时复制”的技术的实现。
现在随着 C++ 的发展,省略的机会越来越多。甚至 C++23 也会有更高级的省略。省略是指数据没有在逻辑上移动或复制,而只是有两个不同的名称,一个在函数内部,一个在函数外部。
依赖它仍然很尴尬。