13

D 有两种 const 类型:不可变变量是被声明为不可变的变量,并且始终是不可变的,而const变量只是对象的只读版本。

逻辑 const是指函数被标记为const,但允许对一个或多个成员变量进行写访问。它的典型用途是惰性求值,例如(在 C++ 中)

struct Matrix
{
  double determinant() const
  {
    if ( m_dirty )
    {
      m_determinant = /* expensive calculation */;
      m_dirty = false;
    }
    return m_determinant;
  }

  void set(int i, int j, double x) { m_dirty = true; ...; }

  mutable bool m_dirty;
  mutable double m_determinant;
};

在这里,determinant()const,但仍然可以修改m_dirty,并且m_determinant由于它们被标记为mutable

D const(FAQ)说 D2 不支持逻辑 const,因为它提供的保证很弱,这阻碍了编写并发程序,并使某些优化更加困难。

我完全理解这个问题,但是如果我们需要逻辑常量怎么办?

考虑上面带有Matrix类的情况,但没有缓存(并且不需要任何逻辑常量)。还可以想象这个类在我的代码库中使用,并且主要通过 const 引用访问。

现在考虑分析表明该determinant()函数是代码中的瓶颈,此外,它通常被重复访问,其值很少改变,即缓存,如上所述,将是一个完美的优化。

如果没有逻辑常量,我怎么能做到这一点?遍历我的代码库将 const 引用更改为非 const 引用不是一种选择(出于显而易见的原因)。

我有哪些选择(如果有)?

4

5 回答 5

11

我认为将最近这个话题的基本结论贴在 D 新闻组这里是合适的,这样那些不跟踪该列表的人仍然可以得到适当的答案。

D 的 const 不是逻辑 const。它是可传递的并且完全是常量。该语言在技术上不支持逻辑常量。该语言没有定义任何改变 const 对象的方法。

实际上,C++ 也没有逻辑 const。使用mutable和抛弃 const-ness 可以让您完全规避 const,这样,从技术上讲,const 实际上并不能保证任何事情,除非您没有在 const 变量上调用任何非 const 函数。const 函数实际上是 const 并且不会与您的变量混在一起这一事实完全按照惯例结合在一起。现在,大多数程序员不会到处抛弃 const-ness 左右并让所有东西都可变,所以在实践中,它非常有用,但它不仅可以完全规避,而且该语言专门为您提供了定义的方法. 在 C++mutable中,丢弃 const 是明确定义的,并受到该语言的支持。

D不这样做。D 的 const 实际上是 const。在变量上抛弃 const 然后更改它是未定义的。没有可变的。D 的 const 有真正的保证(只要你不做任何未定义的事情,比如在某事上抛弃 const 然后对其进行变异)。这很重要,不仅因为编译器对 D 的保证比对 C++ 的保证要强得多,而且因为不可变变量不能以任何方式改变形状或形式。它们可能在只读存储器中,如果你试图抛弃不变性并改变这样的变量,谁知道会发生什么可怕的事情(段错误可能是可能发生的最好的事情)。而且由于 const 变量实际上可以引用不可变的数据,至少可以说,放弃 const 来改变变量或允许 const 变量以某种方式被改变是不好的。所以,语言不允许。

现在,正如BCS 指出的那样,D 是一种实用语言。您可以抛弃 const,此时您可以更改变量。因此,例如,您可以有一个变量用于缓存 const 函数的返回值(如果对象的状态发生更改,该缓存可能会失效)并丢弃 const 来更改它。只要所讨论的变量实际上不是不可变的,它就会起作用。但是,这是未定义的行为。一旦你做到了,你就靠自己了。您正在绕过类型系统和编译器的保证。是负责确保您不会在不可变对象上执行此操作或以其他方式搞砸编译器通常保证的内容的人。所以,如果你需要做到这一点,你可以,但你正在进入狂野的西部,你要确保你不会改变你不应该改变的东西。

鉴于只要变量实际上不引用不可变数据,丢弃 const 就可以工作,因此可以创建一个Mutable模板来从本质上获得mutableC++ 中提供的内容(因此,它会丢弃 const-ness为你)。he_the_great 在他的回答中给出了这样一个模板的例子。但是使用这样的模板仍然是未定义的行为。在实际上不可变的对象上使用它会导致问题。,程序员,必须确保它被正确使用。

因此,D 通过抛弃 const 在技术上使逻辑 const 成为可能,但为了做到这一点,您必须通过绕过类型系统来超出编译器所保证的范围,并且您必须确保不会误用it 和 mutate 不应该/不能改变的变量,否则你的代码会有问题——段错误很可能是其中最少的。

编辑:我忘了提到一个破坏类型系统的建议解决方案。只要您愿意放弃纯度,您就可以使用某种全局变量(无论是在模块范围内、类变量还是结构变量)来保存您的缓存值。const 函数可以自由使用和改变全局变量,因此可以用来代替缺少的mutable. 然而,这确实意味着函数不能是纯函数,这也可能是一个大问题。然而,这是一种让 const 函数仍然能够在不破坏类型系统的情况下改变它需要的数据的方法。

于 2010-12-01T19:51:45.397 回答
4

我已经很久没有接触过 D2,所以您可能需要仔细检查我所说的内容。:)

我不确定你真的有什么好的选择。D 的 const 和 immutable 比 C/C++ 强得多,因此将它们丢弃不是一种选择。您已明确排除在代码中更改 const 的使用。

您可以将运算结果缓存在以矩阵值本身为键的全局哈希表中。这适用于 const/immutable 的任何组合。当然,问题在于 D 没有世界上最快的哈希表,计算哈希可能很慢。也许在创建矩阵时预先计算哈希。

另一种选择是在值发生变化时急切地计算行列式。

除此之外,我想不出别的了。实际上,问题在于您要求编译器使用 const 保护您,然后试图摆脱它。“正确”的解决方案可能只是不使用 const。:P

于 2010-11-19T05:21:00.210 回答
1

作为一种务实的语言,如果你真的需要,D 有能力抛弃 const。我认为以下应该有效:

class M {
  bool set;
  real val;

  real D() const {
    if(!set) {
      M m = cast(M)this;
      m.val = this.some_fn();
      m.set = true;
    }
    return this.val;
  }
}
于 2010-11-19T01:24:30.007 回答
0

我强烈建议 BCS 的答案,因为只要不创建不可变/常量矩阵,它就简单且安全。

另一个有助于使不可变/常量对象保持有效的选项是这个Mutable Template。或者至少这是意图。有关于这些担忧的评论。

使用此模板,需要对引用类型而不是 const 函数中的值进行修改。这意味着使用了指针并且需要为每个矩阵分配空间。这也使得创建一个不会出现段错误的不可变/常量矩阵变得更加困难,也许有一种方法可以很好地做到这一点,但我只知道一个用于类的方法。

struct Matrix
{
    double determinant() const
    {
        if ( *m_dirty )
        {
            *m_determinant = 646.363; /* expensive calculation */;
            *m_dirty = false;
        }
        return *m_determinant;
    }

    void set(int i, int j, double x) { *m_dirty = true; }

    Mutable!(bool*) m_dirty;
    Mutable!(double*) m_determinant;
};
于 2010-11-30T21:14:09.213 回答
0

const为了模仿D 中 C++ 的逻辑,您可以对类使用继承:

class ConstMatrix
{
public:
    double det() { // not marked as const!
        /* ... caching code ... */
    }
    /* ... the rest of the logically const interface ... */
}

class Matrix : ConstMatrix
{
public:
    void set( int row, int col, double val ) {
        /* ... */
    }
    /* ... the non-logically const interface ... */
}

ConstMatrix类实现中,除非您将const限定符放在函数签名上,否则您不会进行任何编译器检查。ConstMatrix但是,如果您使用逻辑常量矩阵,您将获得客户端代码的 const 正确性。

对于结构,您可以使用该alias技术来完成相同的操作:

struct ConstMatrix 
{
    /* logically const bla bla blub */
}

struct Matrix
{
public:
    alias m this;

    /* non-const bla bla blub */
private:
    ConstMatrix m;
}

对于类类型,您可以通过以下方式构建具有逻辑 const 正确性的其他classes 或s:struct

class ConstBiggerClass
{
private:
    ConstMatrix m;
}

class BiggerClass : ConstBiggerClass
{
private:
    Matrix m;
}

这样编译器将为const您检查正确性。但是,您需要一个额外的数据成员。对于class类型中的类型成员class,另一种方法是提供一个成员函数,该函数返回具有正确常量的数据成员:

class ConstBiggerClass
{
public:
    void someLogicallyConstFunction( /*...*/ ) { /* ... */ }
protected:
    abstract ConstMatrix getMatrix();
}

class BiggerClass : ConstBiggerClass
{
public:
    void someMutableFunction( /*...*/ ) { /*...*/ }
protected:
    Matrix getMatrix() { return m; }
private:
    Matrix m;
}
于 2013-05-15T12:18:19.960 回答