C++ 是否支持与 STL 兼容的不可变记录类型?
Does C++ support STL-compatible immutable record types?
许多语言都支持不可变类型——一旦构造了这种类型的值,就无法以任何方式对其进行修改。我不会偏离这些类型的好处,因为这已在别处广泛讨论。 (参见 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
s 可以反弹指向其他值:
//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
ing 类型的数据成员使其成为不可变类型,但也无法创建该类型的 "var"。没有后者,任何人都可以找到实现前者的合理轻量级方法吗? [请注意 "lightweight" 部分——特别是,我不想为 struct
的每个元素创建访问器函数,因为这会导致可读性大幅下降。]
请记住,大多数其他语言中的赋值行为类似于 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
如果您想模仿您的 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;
您提到您不能将 Coordinates
class 与 const
成员编写的 STL 容器一起使用。如何做到这一点的问题在这里得到了回答(不确定):
关于此问题的评论线程中也提出了相同的建议,即让您的实例 const
而不是您的成员。我同意你的评论,因为你必须更改更多代码,所以它不太可靠,但也许你可以像这样使用 typedef:
struct MutableCoordinates {
double x;
double y;
};
typedef const MutableCoordinates Coordinates;
以便 Coordinates
对象的用户自动获得不可变版本,除非他们明确要求它?这将抵消您对安全使用结构的冗长性的一些担忧。
当然,您也可以使用 shared_ptr
切换到引用语义,这对于 const
对象来说效果很好,因为不同的引用不会相互混淆,但它附带手动内存分配的开销对于两个 double
s.
的结构来说是相当大的
许多语言都支持不可变类型——一旦构造了这种类型的值,就无法以任何方式对其进行修改。我不会偏离这些类型的好处,因为这已在别处广泛讨论。 (参见 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
vsvar
: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
s 可以反弹指向其他值:
//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
ing 类型的数据成员使其成为不可变类型,但也无法创建该类型的 "var"。没有后者,任何人都可以找到实现前者的合理轻量级方法吗? [请注意 "lightweight" 部分——特别是,我不想为 struct
的每个元素创建访问器函数,因为这会导致可读性大幅下降。]
请记住,大多数其他语言中的赋值行为类似于 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
如果您想模仿您的 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;
您提到您不能将 Coordinates
class 与 const
成员编写的 STL 容器一起使用。如何做到这一点的问题在这里得到了回答(不确定):
关于此问题的评论线程中也提出了相同的建议,即让您的实例 const
而不是您的成员。我同意你的评论,因为你必须更改更多代码,所以它不太可靠,但也许你可以像这样使用 typedef:
struct MutableCoordinates {
double x;
double y;
};
typedef const MutableCoordinates Coordinates;
以便 Coordinates
对象的用户自动获得不可变版本,除非他们明确要求它?这将抵消您对安全使用结构的冗长性的一些担忧。
当然,您也可以使用 shared_ptr
切换到引用语义,这对于 const
对象来说效果很好,因为不同的引用不会相互混淆,但它附带手动内存分配的开销对于两个 double
s.