许多语言都支持不可变类型 - 一旦构造了这种类型的值,就无法以任何方式对其进行修改。我不会偏离这些类型的好处,因为这已在其他地方广泛讨论过。 (参见例如http://codebetter.com/patricksmacchia/2008/01/13/immutable-types-understand-them-and-use-them/)
我想在C ++中创建一个轻量级的不可变记录类型。这样做的一个显而易见的方法是const
所有成员。例如:
struct Coordinates {
const double x;
const double y;
};
不幸的是,以这种方式声明的类型不支持复制赋值,因此不能与STL容器一起使用,这是一个非常致命的缺点。我认为没有办法解决这个问题,但是如果有人能看到一个,我将不胜感激。
对于它的价值,据我所知,C ++正在混淆两件事:a)你是否可以为变量赋值,b)是否可以在它们之后修改“values”(= objects)是建造的。例如,区别更清楚。 Scala,其中一个人
val
vs var
:var
可以绑定一个新值,val
不能所以我可以写下以下内容:
val val_mutable = collection.mutable.Map(1 -> "One")
val val_immutable = collection.immutable.Map(1 -> "One")
var var_mutable = collection.mutable.Map(1 -> "One")
var var_immutable = collection.immutable.Map(1 -> "One")
var
可以反弹以指向其他值:
//FAILS: val_immutable = collection.immutable.Map(2 -> "Two")
//FAILS: val_mutable = collection.mutable.Map(2 -> "Two")
var_mutable = collection.mutable.Map(2 -> "Two")
var_immutable = collection.immutable.Map(2 -> "Two")
可修改的集合可以修改:
val_mutable(2) = "Two"
//FAILS: val_immutable(2) = "Two"
var_mutable(2) = "Two"
//FAILS: var_immutable(2) = "Two"
在C ++中,const
类型的数据成员使其成为不可变类型,但也无法创建该类型的“var”。没有后者,任何人都可以看到实现前者的合理轻量级方式吗? [请注意“轻量级”部分 - 特别是,我不想为struct
的每个元素创建一个访问器函数,因为这会导致可读性大幅下降。]
答案 0 :(得分:6)
请记住,大多数其他语言中的赋值行为类似于C ++中的指针重新分配。
因此,C ++中的std::shared_ptr<const T>
与其他语言中的不可变类型具有相似的语义。
struct Coordinates {
double x;
double y;
};
std::shared_ptr<const Coordinates> p = std::make_shared<const Coordinates>(Coordinates{3.14, 42.0});
p->x *= 2; // error; pointee is immutable
p = std::make_shared<const Coordinates>(Coordinates{6.28, 42.0}); // ok
答案 1 :(得分:1)
如果您想模仿var
/ val
,可以创建2个类似于以下的类:
template <typename T> class val
{
public:
val(const T& t) : t(t) {}
val(const val&) = default;
val& operator = (const val&) = delete;
const T* operator->() const { return &t; }
const T& get() const { return t; }
T* operator->() { return &t; }
T& get() { return t; }
private:
T t;
};
template <typename T> class var
{
public:
var(const T& t) : t(t) {}
var(const var&) = default;
var& operator = (const var&) = default;
var& operator = (const T&) { this->t = t; return *this; };
const T* operator->() const { return &t; }
const T& get() const { return t; }
T* operator->() { return &t; }
T& get() { return t; }
private:
std::remove_const_t<T> t;
};
然后
val<const Coordinate> immutable_val({42, 51});
val<Coordinate> mutable_val({42, 51});
var<const Coordinate> immutable_var({42, 51});
var<Coordinate> mutable_var({42, 51});
//immutable_val->x = 42; // error
mutable_val->x = 42;
//immutable_var->x = 42; // error
mutable_var->x = 42;
//immutable_val = {42, 42}; // error
//mutable_val = {42, 42}; // error
immutable_var = {42, 42};
mutable_var = {42, 42};
//mutable_val = mutable_val; // error
//immutable_val = immutable_val; // error
immutable_var = immutable_var;
mutable_var = mutable_var;
答案 2 :(得分:1)
您提到您不能将Coordinates
类与使用const
成员编写的STL容器一起使用。如何做到这一点的问题在这里得到了回答(不确定):
https://stackoverflow.com/a/3372966/393816
同样的建议,即使你的实例const
而不是你的成员,也在这个问题的评论主题中提出。我同意你的评论,因为你必须更改更多的代码它不太可靠,但也许你可以使用类似的typedef:
struct MutableCoordinates {
double x;
double y;
};
typedef const MutableCoordinates Coordinates;
那么Coordinates
个对象的用户会自动获取不可变版本,除非他们明确地这样做了吗?这将抵消您对安全使用结构的详细程度的担忧。
当然,您也可以使用shared_ptr
切换到引用语义,这对于const
个对象非常有效,因为不同的引用不会相互混淆,但它附带了手动内存分配的开销对于两个double
s的结构来说相当过分。