D 中的逻辑常量

发布于 2024-10-03 02:08:46 字数 1127 浏览 8 评论 0原文

D 有两种类型的常量:不可变变量是被声明为不可变的变量,并且始终是不可变的,而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_dirtym_determinant 因为它们被标记为mutable

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

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

考虑上面使用 Matrix 类的情况,但没有缓存(并且不需要逻辑 const)。还想象一下,这个类在我的代码库中到处使用,并且主要通过 const 引用进行访问。

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

如果没有逻辑常量,我该如何做到这一点?在我的代码库中将 const 引用更改为非 const 引用并不是一个选择(出于明显的原因)。

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

D has two types of constness: immutable variables are ones that were declared immutable, and always will be immutable, while const variables are simply read only versions of an object.

Logical const is when a function is marked as const, but allows write access to one or more member variables. The typical use of this is for lazy evaluation, e.g. (in 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;
};

Here, determinant() is const, but can still modify m_dirty and m_determinant due to them being marked as mutable.

The D const(FAQ) says that D2 doesn't support logical const because of the weak guarantee that it provides, which is a hinderance to writing concurrent programs, and makes certain optimisations more difficult.

I completely understand the concern, but what if we need logical const?

Consider the case above with the Matrix class, but without caching (and any need for logical const). Also imagine that this class is used all over my codebase, and is mostly accessed through const references.

Now consider that profiling has revealed that the determinant() function is a bottleneck in the code, and furthermore it is usually accessed repeatedly with its value rarely changing i.e. caching, as above, would be a perfect optimisation.

How can I do that without logical const? Going all over my codebase changing const references to non-const references is not an option (for obvious reasons).

What options do I have (if any)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

2024-10-10 02:08:46

我认为发布 的基本结论是适当的最近在 D 新闻组 上讨论了这个主题,以便那些不跟踪该列表的人仍然可以获得适当的答案。

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

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

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

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

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

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

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

I think that it would be appropriate to post the basic conclusions of the recent thread on this topic on the D newsgroup here, so that those who don't track that list can still get the appropriate answer.

D's const is not logical const. It is transitive and fully const. The language does not technically support logical const. The language does not define any way to mutate a const object.

And actually, C++ doesn't have logical const either. Using mutable and casting away const-ness allows you to totally circumvent const, such that, technically-speaking, const doesn't actually guarantee anything except that the you aren't calling any non-const functions on a const variable. The fact that const functions are actually const and don't screw with your variables is completely held together by convention. Now, most programmers don't go around casting away const-ness left and right and making everything mutable, so in practice, it's quite useful, but it not only can be completely circumvented, but the lanuage specifically gives you defined means of doing so. In C++ mutable and casting away const are well-defined and supported by the language.

D doesn't do that. D's const is actually const. Casting away const on a variable and then altering it is undefined. There is no mutable. D's const has real guarantees (as long as you don't do anything undefined like cast away const on something and then mutate it). This is important, not only because the compiler guarantees for D are much stronger than those for C++, but because immutable variables can't be altered in any way shape or form. They could be in read-only memory, and who knows what horrid things would happen if you tried to cast away immutability and alter such a variable (a segfault would likely be the nicest thing that could happen). And since a const variable could actually refer to immutable data, casting away const to alter a variable or allowing for const variables to somehow be altered would be bad, to say the least. So, the language doesn't allow it.

Now, as BCS points out, D is a pragmatic language. You can cast away const, at which point you could alter the variable. So, for instance, you could have a variable which was used to cache the return value of a const function (presumably with that cache being invalidated if the state of the object changed) and cast away const to change it. As long as the variable in question is not actually immutable, it will work. However, this is undefined behavior. Once you do it, you're on your own. You're bypassing the type system and the compiler's guarantees. You are the one responsible for making sure that you don't do it on an immutable object or otherwise screw up what the compiler normally gurantees. So, if you need to do it, you can, but you're stepping out into the Wild West, and it's up to you to make sure that you aren't mutating what you shouldn't.

Given that casting away const will work as long as the variable doesn't actually refer to immutable data, it is possible to create a Mutable template to essentially get what mutable gives you in C++ (so, it'll do the casting away of const-ness for you). he_the_great gives an example of such a template in his answer. But using such a template is still undefined behavior. Using it on an object which is actually immutable is going to cause problems. You, the programmer, must make sure that it's used correctly.

So, D makes it technically possible to have logical const by casting away const, but in order to do it, you have to step outside of what the compiler guarantees by bypassing the type system, and you must make sure that you don't misuse it and mutate variables which shouldn't/can't be mutated, or your code will have problems - segfaults quite possibly being the least among them.

EDIT: I forgot to mention the one proposed solution which does not break the type system. As long as you're willing to forgoe purity, you can use a global variable of some variety (be it at module scope, a class variable, or a struct variable) to hold your cached values. The const function can freely use and mutate the global variables, so it can be used in lieu of the missing mutable. That does mean, however, that the function can't be pure, which could also be a big problem. It is, however, a way to have a const function still be able to mutate the data that it needs to without breaking the type system.

踏雪无痕 2024-10-10 02:08:46

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

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

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

另一种选择是当值发生变化时立即计算行列式。

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

I haven't touched D2 in ages, so you might want to double-check what I say. :)

I'm not sure you really have any good options. D's const and immutable are significantly stronger than C/C++'s, so casting them away isn't an option. You've explicitly ruled out changing your usage of const in your code.

You could cache the result of the operation in a global hashtable keyed on the matrix value itself. That will work for any combination of const/immutable. The problem with that, of course, is that D doesn't have the fastest hashtables in the world and computing the hash could be slow. Maybe pre-compute the hash when creating the matrix.

The other option would be to compute the determinant eagerly when the value changes.

Aside from that, I can't think of anything else. The problem, really, is that you're asking the compiler to protect you with const and then trying to break out of it. The "proper" solution is probably to just not use const. :P

屋顶上的小猫咪 2024-10-10 02:08:46

作为一种实用语言,如果您确实需要的话,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;
  }
}

Being a pragmatic language, D has the ability to cast away const if you really need to. I think the following should work:

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;
  }
}
傲性难收 2024-10-10 02:08:46

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

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

使用此模板,需要对引用类型进行修改,而不是对 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;
};

I highly suggest BCS's answer as it is simple and safe as long as an immutable/const Matrix is not created.

Another option that helps to make even immutable/const objects remain valid is this Mutable Template. Or at least that is the intent. There are comments about the concerns.

With this template, it is required that modification is done to a referenced type and not a value in a const function. This means pointers are used and require space to be allocated for each Matrix. This also makes it harder to create an immutable/const Matrix that won't segfault, maybe there is a way to do it nicely but I only know of one for classes.

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;
};
心头的小情儿 2024-10-10 02:08:46

要在 D 中模拟 C++ 中的逻辑 const,您可以使用类的继承:

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<,否则您不会进行任何编译器检查。 /code> 限定符到函数签名上。但是,如果您将 ConstMatrix 用于逻辑常量矩阵,您将获得客户端代码的 const 正确性。

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

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

struct Matrix
{
public:
    alias m this;

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

对于类类型,您可以构建具有逻辑常量正确性的其他classstruct通过以下方式:

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;
}

To mimic logical const from C++ in D you can use inheritance for classes:

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 ... */
}

In the ConstMatrix class implementation you won't have any compiler checks unless you put const qualifiers onto the function signatures. However, you will get const correctness for client code, if you use ConstMatrix for logically constant matrices.

For structs you can use the alias technique to accomplish the same:

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

struct Matrix
{
public:
    alias m this;

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

For class types you can build other classes or structs with logical const correctness in the following way:

class ConstBiggerClass
{
private:
    ConstMatrix m;
}

class BiggerClass : ConstBiggerClass
{
private:
    Matrix m;
}

This way the compiler will check const correctness for you. However, you'll need an extra data member. For class type members in class types, an alternative would be to provide a member function which returns the data member with the right constness:

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

class BiggerClass : ConstBiggerClass
{
public:
    void someMutableFunction( /*...*/ ) { /*...*/ }
protected:
    Matrix getMatrix() { return m; }
private:
    Matrix m;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文