如何在 D 中声明一个指向非常量/可变数据的 const 指针?

How do I declare a const pointer to non-const / mutable data in D?

在 D 中,如何在 D 中声明指向非常量/可变数据的 const 或不可变指针?

dlang 网站说你不能只将它声明为 const,因为这使得指针 const 和它指向的数据都是不可修改的。

我读过与此相关的早期帖子,表明这根本不可能。如果是这样,那就是语言设计中的一个大漏洞。应该可以单独将指针声明为不可修改的,否则就是疯狂的。让 const 从指针传播到也暗示 const 数据可能是一个有用的默认安全功能。

你不知道。在D中,constimmutableshared都是可传递的。因此,一旦类型的外部部分是 const(或 immutableshared),整个类型就是。有一次,在 D2 早期 非常,该语言既有头部 const 也有尾部 const,但它被认为太复杂而不值得,而且它被删除(回到版本 2.015 IIRC)。所以,现在 constimmutableshared 是完全可传递的,多年来一直如此。

你可以声明像

这样的东西
const(int)* p;

这样类型的内部部分是 const,但是没有办法表明外部部分是 const 而不使其中的所有内容也都成为 const

所以,是的,你想做的事情在 D 中是不可能的,它可能不如理想的灵活,但 head const 也是 [=11 中最没用的形式=].所以,虽然这可能是一个损失,但据我所知,它确实不是一个大损失。允许 head const 确实 会使事情复杂化 - 特别是当 immutableshared 发挥作用时。因此,当前的系统要简单得多,而且不会损失太多功率,可以说是一个很好的权衡。

如果你真的想要像 head const 这样的东西,你总是可以创建一个不允许赋值的包装器类型,但这是你将要获得的最接近的类型。

不幸的是,D 似乎不直接支持它,尽管我认为,对于指向非常量数据的常量指针的合理用例肯定比 可变指针值(非常量指针大多是不需要的)。

但还没有结束!

D 对其“tail-const”理念并不太严格,这是合理的,否则像按引用调用这样的常见机制将无法工作。 D 确实有“head-const”指针,但它们被混淆地称为“references”(我不是在谈论使用的引用类型变量对于 class 类型的变量)。

据我了解,D出于安全原因正式引入了引用,因此可以定义引用的地方仅限于发生接口的地方(函数参数和foreach变量).您不能在代码中自由定义引用。

最好将 D 中的引用解释为常量指针,无论何时使用它们,它们都会自动 间接。与可变指针相比的一大优势是,您不能将它们重新分配给另一个内存区域,而且它们不太可能或实际上永远不会 null。尽可能使用引用并避免使用指针。唯一的问题:D 的 ref 比 C++ 更受限制,所以如何确保在不将对象移动到堆栈上的情况下传播实际引用?

D 中引用的实际目的是表示输入+输出参数(在函数或 foreach 循环中更新的参数,或者当用作 return 值时由调用者更新的参数)而不是输入或仅输出参数。

请注意,还有一个 inout 关键字,但它具有不同的含义(它是一个代表 constimmutable 和非常量).

对于仅输出参数,您使用 out。对于仅输入参数,您可以使用较新的 in,它根据文档允许左值 右值参数。

如果要将指针分配给引用,请使用指针间接寻址:ref int x = *pointer。如果您想将引用分配给可变指针,请使用地址:int* x = &reference.

现在我们开始了解如何规避 D 的受限指针哲学:

int x = 3, k = 3;

ref int getInt(int n = 3 /*does nothing*/) {
    return .x;  // use '.globalVar' for readability
}
int* getIntPointer() {
    return &.x;   
}
void updateInt(ref int x) {
    x += 1;
}

void main()
{
    import std.stdio : writeln;
    int i = getInt();
    updateInt(i);
    writeln(.x);
    writeln(i);
    (ref int j = getInt(k)) {   // using 'k' is no problem
        updateInt(j);
        writeln(.x);
        writeln(j);
    }();    // this is a lambda expression
    (ref int j = *getIntPointer()) {
        updateInt(j);
        writeln(.x);
        writeln(j);
    }();

    S s = S();
    s.update(5);
    writeln(.x);
    writeln(s.y);
}

struct S {
    static int y = 5;
    static ref int getSetInt(int n) {
        return .x = n;
    }
    void update(int k) {
        (ref int j = S.y) { // does not work with this.y
            .updateInt(j);
        }();
        (ref int j = getSetInt(k)) {    // does not work with instance methods
            .updateInt(j);
        }();
    }
}

值得注意的是,您不能在 lambda 表达式的默认参数值中使用来自 struct/class 对象的实例成员或方法。我想是因为 lambda 表达式在内部被视为静态的。这就是为什么不能在默认值中使用 this 的原因(静态 class/struct 方法除外)。

这种方法显然有一个缺点。代码块 - 看起来像函数式编程语言中的 let-block - 实际上是一个 lambda 表达式,代码块中的 a return 语句只会 return 来自 lambda 的控制表达式,而不是来自它周围的函数,这令人困惑。

事实证明,return从函数中获取引用更加棘手:

return *(ref int var = value) { return &var; }();  // cannot return references, only pointers
return *((ref int var = value) => &var)();  // can't avoid the inner pair of parentheses

当然你不应该这样做,除非你确切地知道 value 在调用者中是有效的。好的事情是,D 要求您对自己所做的事情保持清醒的认识。您不能只将 returned 引用从 lambda 传递给 return 语句或指针变量,因为 returned 引用是右值。

当然你也可以定义多个引用:

(ref int var1 = val1, ref int var2 = val2, ...) {
    ...
}();

关于指向非常量数据的常量指针会很复杂的论点是行不通的。 D 一直以(过于)受限的语法方式使用它,这并不妨碍您按需要使用它(幸运的是)。 D 的哲学在这里使代码更加冗长,但至少让程序员更难 return 悬挂引用,这样就不太可能意外发生。