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

时间:2016-07-06 04:35:37

标签: pointers const d immutability variable-declaration

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

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

我之前阅读过有关此事的帖子,表明这根本不可能。如果是这样,那么这就是语言设计中的一个大漏洞。应该可以将指针单独声明为不可修改的,否则它是疯狂的。使const从指针传播也意味着const数据可能是一个有用的默认安全功能。

2 个答案:

答案 0 :(得分:5)

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

您可以声明

之类的内容
const(int)* p;

这样类型的内部部分是const,但是没有办法表明外部部分是const而没有在其中创建所有内容const

所以,是的,你想要做的事情在D中是不可能的,这可能不如理想的那么灵活,但是头部const也是const最不实用的形式。 。所以,虽然它可能是一种损失,但就我所知,它确实不是一个大问题。并允许头部const 真正使事情复杂化 - 特别是当immutableshared发挥作用时。因此,当前系统更简单,不会失去太多功率,这可以说是一个非常好的权衡。

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

答案 1 :(得分:0)

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

但这不是最后一句话!

D 对其哲学不是很严格,合理地,否则像按引用调用这样的常见机制将不起作用。 D 确实有头常量指针,但它们被混淆地称为“引用”(我不是在谈论用于类类型变量的引用变量)。

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

最好将 D 中的引用解释为常量指针,每当您使用它们时,这些指针自动被间接引用。与可变指针相比的一大优势是,您不能将它们重新分配给另一个内存区域,而且它们不太可能 null(您可以将它们视为不是 null),甚至可能是不可能的,这使得它们比可变指针更安全,而且实际上总是更可取(我今天不鼓励使用它,即使是与 C 接口也不行)。

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

注意,还有一个 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 表达式的默认参数值中使用来自结构/类对象的实例成员或方法。我想是因为 lambda 表达式在内部被视为静态的。这就是您不能在默认值中使用 this 的原因(静态类/结构方法除外)。

这种方法显然有一个缺点。代码块——在函数式编程语言中看起来像一个 let 块——实际上是一个 lambda 表达式,并且代码块中的 return 语句只会从 lambda 表达式而不是从函数返回控制权周围令人困惑。

事实证明,从函数返回引用更加棘手:

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 要求您注意自己的行为。您不能只将 lambda 返回的引用传递给 return 语句或指针变量,因为返回的引用是一个右值。

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

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

关于指向非常量数据的常量指针会很复杂的论点并不成立。 D 一直以(太)受限制的语法方式使用它,这不会阻止您在需要时使用它(幸运的是)。 D 的哲学使这里的代码更加冗长,但至少让程序员更难返回悬空引用,因此不太可能发生意外。