【问题标题】:Why doesn't scala have C++-like const-semantics?为什么 scala 没有类似 C++ 的 const 语义?
【发布时间】:2011-04-21 05:46:25
【问题描述】:

在 C++ 中。我可以将大多数东西声明为 const,例如:
变量:const int i=5;
Scala 有 val i=5,但是这只会阻止重新分配,而不是更改对象,如下例所示:
C++:

const int i[]={1,2,3,4};
i[2]=5; //error
斯卡拉:
val a=Array(1,2,3,4)
a(2)=5 //a is now Array(1, 2, 5, 4)

成员函数会变得更糟:
C++:

class Foo {
int i;
int iPlusFive() const {return i+5;}
int incrementI(){ return ++i; }
}
我可以肯定,调用 iPlusFive 不会更改对象,并且我不会意外地在 const 对象上调用 incrementI。

在集合方面,C++ 继续使用 const 集合保持 const 正确性:只需将向量声明为 const 即可,您无法更改它。将 non-const vector<Int> 分配给 const vector<Int>,编译器不会复制任何内容,并且会阻止您更改 now const 集合中的任何内容。

Scala 有 scala.collection.mutable.whatever 和 scala.collection.immutable.whatever,您不能只将可变集合转换为不可变集合,此外,您仍然可以使用它们的非常量成员更改收集的对象功能。

为什么具有非常棒的类型系统的 scala 没有任何可以与 C++ const-keyword 相媲美的东西?

编辑: Margus 建议使用import scala.collection.mutable。 我的解决方案是使用

import scala.collection.mutable.HashMap
import scala.collection.immutable.{HashMap => ConstHashMap}
这将使可变 HashMap 可用作 HashMap 和不可变 als ConstHashMap,但是我仍然更喜欢 C++ 方法。

【问题讨论】:

  • 我将此添加为评论,因为它并不是真正的答案。我能想出的唯一答案是它不是“设计使然”。事实上,很少有编程语言具有与 C++ 相同的 const 语义。有些根本没有,其他的像 Java/C# 有一个有限的形式(类似于 scala),它阻止代码重新分配给 reference,但不能保证 referred 对象(原始类型在某些情况下的处理方式不同,可以设为常量)。
  • 对刚刚创建的、有意使用而没有突变的东西进行突变基本上是错误的,并且不受静态编译器检查的影响。但是把它传递给一个需要“非变异”接口的方法——那里不可能有变异(即使是错误的)。所以仅仅从适当的接口继承就可以从根本上解决问题。
  • C++ 本身并不能阻止我更改所指的内容。写const int i(5); const int * cip(&i); int * ip(const_cast<int *>(cip)); *ip = 6; 之类的东西是未定义的行为,但这并不意味着任何事情都会阻止你。
  • 如果你还没有读过james-iry.blogspot.com/2010/07/when-constants-vary.html,C++ 中 const 的实用性和保证有一些严重的限制。
  • 那篇博客文章很好地解释了const 的实际效果与假设const == 不变的人们的期望。如果您认为函数的签名是定义调用者赋予被调用者期望和更改的权利的合同,那么突然const 的行为(以及隐式添加 cv-qualification) 非常有意义。在另一个世界中,const 表示常量并承诺被调用者参数不会改变,那么隐式转换将是 remove cv-qualification

标签: c++ scala types constants


【解决方案1】:

我不喜欢 C++ const 逻辑的一点是它与引用有关,而不是与被引用的对象有关。如果我有一个“const T *”,则不能保证持有非常量指针的人不会修改对象的状态。因此,它无助于避免多线程系统中的竞争条件,也无助于实现持久容器。

在我看来,拥有不可变类的概念是非常有帮助的,标准库中缺少不可变容器在任何语言中都是一个错误。由于这些应该表现出可观察的不变性,但出于效率原因可能需要能够更改内部/不可见状态,我认为 const-syntax 没有什么帮助。

Scala 具有我们需要直接使用或基于其他不可变类的不可变类。这是极其宝贵的。额外的语法可能是一个很好的补充,但我可以没有它。

【讨论】:

  • 你在说什么? const T* - 或更好的 T const* 因为 const 向左工作使 T 保持不变并使 T* 可变。因此,除非有人使用丑陋的 hack - 或者您将一个非 const 指针从您的班级中递出,否则没有人可以更改数据。
  • 我说的是被交给一个指向我不拥有或控制的对象的 const 指针。由于我不知道其他人是否持有非常量指针,因此我无法修改该对象,但不能保证它不会被修改。
  • 顺便说一句:"const int *x" 使 x 可变且 *x 不可变,而 "int *const x" 使 x 不可变且 *x 可变。
  • 这种对 敏感的行为让我着迷……您的变体“int const *x”是“const int *x”的同义词。
  • Martin 是绝对正确的,得到一个 const 指针并不意味着没有其他人可以更改该对象。
【解决方案2】:

因为 C++ const 在复杂系统中并不是那么好。

我可以肯定,调用 iPlusFive 不会更改对象,并且我不会意外地在 const 对象上调用 incrementI。

不,你不能,因为实现(可能在某个看不见的库中)可以消除 constness。如果没有 const,其他语言必须以更安全的方式强制执行不变性(例如,请参阅 @Margus 的答案中的 Collections.unmodifiableList())。

Const 只是编译器读取的文档。文档通常很有帮助,但有时会产生误导。

在集合方面,C++ 继续使用 const 集合保持 const 正确性:只需将向量声明为 const 即可。

聚合是我经常崩溃的地方。例如,我经常想声明一个方法不会改变向量,但可以改变它的一个成员(或返回一个非常量成员引用)。我必须将其全部设为 const 或全部为 nonconst,或者为每种组合发明新的类型变体。

“可变”弥补了一些聚合问题,但引入了更多的复杂性和误用。

【讨论】:

    【解决方案3】:

    IMO,Scala 只是提供了更大的灵活性,不会将引用的不变性与其背后结构的 [可能] 可变性混为一谈,让您能够针对您正在处理的问题做出设计决策。

    【讨论】:

    • 你是说 C++ 没有这种灵活性吗?这在 C++ 中工作得很好:const vector<Point*>& v = getPoints(); v[0]->x++;。数据结构(向量)的常量和内容(Point)是分开的。
    • 我说的是数据结构与引用的不变性,而不是数据结构与其内容的不变性。例如,您不能从不可变数据结构中添加/删除元素,而内容(单个元素)的突变是可能的。
    • 这在 C++ 中是完全可能的。如果你有一个数据结构class Collection { T* elements; public: void const_func(int i) const; },那么写void Collection::const_func(int t) const { elements[i]++; elements[i+1] = members[i-1]; }在一个const成员函数中没有问题,this的类型是const Collection* const this,数据成员变成T* const elements而不是const T* const elements。当然const Collection*Collection* const 不同,Collection* const 是参考的不变性与您最初提出的实例问题的不变性。
    • 当然,如果你说class ImmutableCollection { const T* const elements; };,那么集合确实是不可变的,没有人在构造后添加或删除实例。
    【解决方案4】:

    所有的Scala代码都被翻译成Java代码,这就是我用Java做例子的原因。

    这是在 Java 中执行此操作的方法:

        Integer x[] = new Integer[]{1,2,3,4}; 
        final List<Integer> CONST = Collections.unmodifiableList(Arrays.asList(x));
    

    所以你问发生了什么? CONST 距离实际数组只有 1 步,它可以隐藏实际数组和 Collection 元素,如果使用简单数组,这是不可能的。如果您仍想修改常量集合,则需要对其进行复制并进行修改。

    来源:链接

    内容不可修改的数组?

    Java 不提供 a 的概念 const 数组:即一个数组 可以换成一个新数组(在 C++ 中 术语,“指针可以修改”), 但其元素无法更改。 但是,如果你需要这个 功能,解决方案是 通常很喜欢提供 对任何其他对象的只读访问 如下所述。所以一对 可能性是:

    • 您可以通过将列表传递到 Collections.unmodifiableList() (虽然 在这种情况下,变量将是 声明为 List 类型,而不是类型 将其标记为“不可修改”——如 下面讨论,任何试图修改 它会在运行时被发现);
    • 您可以围绕私有数组创建包装对象,并提供 读但不写的公共方法 它的元素;
    • 您可以使用只读的 IntBuffer(或 FloatBuffer 等):...

    来源:Java equivalents - const 从 C++ 的角度来看。

    Scala 的等价物是:

        val A = Set(1, 2, 3, 4)
    

        val A = List(1, 2, 3, 4)
    

    这应该翻译成:

        scala.collection.immutable.List[java.lang.Integer] A = List(1, 2, 3, 4)
    

    这可以解释我对 IttayD 评论的回应,为什么 java 6 与 c++ 编译器不同:

    具体化的泛型

    目前,实现了泛型 使用擦除,这意味着 泛型类型信息不是 在运行时可用,这使得一些 那种很难写的代码。泛型 以这种方式实施以支持 向后兼容旧版本 非通用代码。具体化的泛型 会使泛型类型 运行时可用的信息, 这将打破传统的非泛型 代码。然而,尼尔·加夫特 建议使类型仅可具体化 如果指定,以免破坏 向后兼容。

    来源:link

    说起来很傻,但是 Java 常量命名约定是为变量使用大写字母。其他阅读您的代码的人会立即知道该标识符是一个无法更改的固定常量值。

    来源:link

    ...您不能只将可变集合转换为不可变集合,...

    如果您想同时使用可变和不可变版本的集合,一个有用的约定是只导入包 collection.mutable。

    import scala.collection.mutable
    

    来源:Scala Collection API

    不确定你所说的转换是什么意思,但你可能可以这样做:

    val a = scala.collection.mutable.List[Int](1, 2, 3)
    val A = scala.collection.immutable.List[Int](a.toArray())
    

    【讨论】:

    • 类似的结构应该是List(来自scala.colleciton.immutableval a = List(1, 2, 3, 4),可以按需转换为数组a toArray
    • 猜你的没错,我用 set 表示在常量集合中你可能也不希望重复。
    • 在 C++ 中,编译器检查 const 访问。尝试在您的示例中修改 CONST 将通过编译并在运行时失败
    • 这就是所谓的Reification,这就是Java做事的方式。
    • "所有的 Scala 代码都被翻译成 Java 代码,这就是我用 Java 做例子的原因。" : 绝对 +1,因为不在 ARM 微码或 smth 中做例子 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-19
    • 1970-01-01
    • 2016-07-01
    • 2011-05-08
    • 1970-01-01
    • 2011-07-16
    • 2015-05-16
    相关资源
    最近更新 更多