如何在 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中,const
、immutable
、shared
都是可传递的。因此,一旦类型的外部部分是 const
(或 immutable
或 shared
),整个类型就是。有一次,在 D2 早期 非常,该语言既有头部 const
也有尾部 const
,但它被认为太复杂而不值得,而且它被删除(回到版本 2.015 IIRC)。所以,现在 const
、immutable
和 shared
是完全可传递的,多年来一直如此。
你可以声明像
这样的东西
const(int)* p;
这样类型的内部部分是 const
,但是没有办法表明外部部分是 const
而不使其中的所有内容也都成为 const
。
所以,是的,你想做的事情在 D 中是不可能的,它可能不如理想的灵活,但 head const
也是 [=11 中最没用的形式=].所以,虽然这可能是一个损失,但据我所知,它确实不是一个大损失。允许 head const
确实 会使事情复杂化 - 特别是当 immutable
和 shared
发挥作用时。因此,当前的系统要简单得多,而且不会损失太多功率,可以说是一个很好的权衡。
如果你真的想要像 head const
这样的东西,你总是可以创建一个不允许赋值的包装器类型,但这是你将要获得的最接近的类型。
不幸的是,D 似乎不直接支持它,尽管我认为,对于指向非常量数据的常量指针的合理用例肯定比 可变指针值(非常量指针大多是不需要的)。
但还没有结束!
D 对其“tail-const”理念并不太严格,这是合理的,否则像按引用调用这样的常见机制将无法工作。 D 确实有“head-const”指针,但它们被混淆地称为“references”(我不是在谈论使用的引用类型变量对于 class 类型的变量)。
据我了解,D出于安全原因正式引入了引用,因此可以定义引用的地方仅限于发生接口的地方(函数参数和foreach变量).您不能在代码中自由定义引用。
最好将 D 中的引用解释为常量指针,无论何时使用它们,它们都会自动 间接。与可变指针相比的一大优势是,您不能将它们重新分配给另一个内存区域,而且它们不太可能或实际上永远不会 null
。尽可能使用引用并避免使用指针。唯一的问题:D 的 ref
比 C++ 更受限制,所以如何确保在不将对象移动到堆栈上的情况下传播实际引用?
D 中引用的实际目的是表示输入+输出参数(在函数或 foreach 循环中更新的参数,或者当用作 return 值时由调用者更新的参数)而不是输入或仅输出参数。
请注意,还有一个 inout
关键字,但它具有不同的含义(它是一个代表 const
、immutable
和非常量).
对于仅输出参数,您使用 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 悬挂引用,这样就不太可能意外发生。
在 D 中,如何在 D 中声明指向非常量/可变数据的 const 或不可变指针?
dlang 网站说你不能只将它声明为 const,因为这使得指针 const 和它指向的数据都是不可修改的。
我读过与此相关的早期帖子,表明这根本不可能。如果是这样,那就是语言设计中的一个大漏洞。应该可以单独将指针声明为不可修改的,否则就是疯狂的。让 const 从指针传播到也暗示 const 数据可能是一个有用的默认安全功能。
你不知道。在D中,const
、immutable
、shared
都是可传递的。因此,一旦类型的外部部分是 const
(或 immutable
或 shared
),整个类型就是。有一次,在 D2 早期 非常,该语言既有头部 const
也有尾部 const
,但它被认为太复杂而不值得,而且它被删除(回到版本 2.015 IIRC)。所以,现在 const
、immutable
和 shared
是完全可传递的,多年来一直如此。
你可以声明像
这样的东西const(int)* p;
这样类型的内部部分是 const
,但是没有办法表明外部部分是 const
而不使其中的所有内容也都成为 const
。
所以,是的,你想做的事情在 D 中是不可能的,它可能不如理想的灵活,但 head const
也是 [=11 中最没用的形式=].所以,虽然这可能是一个损失,但据我所知,它确实不是一个大损失。允许 head const
确实 会使事情复杂化 - 特别是当 immutable
和 shared
发挥作用时。因此,当前的系统要简单得多,而且不会损失太多功率,可以说是一个很好的权衡。
如果你真的想要像 head const
这样的东西,你总是可以创建一个不允许赋值的包装器类型,但这是你将要获得的最接近的类型。
不幸的是,D 似乎不直接支持它,尽管我认为,对于指向非常量数据的常量指针的合理用例肯定比 可变指针值(非常量指针大多是不需要的)。
但还没有结束!
D 对其“tail-const”理念并不太严格,这是合理的,否则像按引用调用这样的常见机制将无法工作。 D 确实有“head-const”指针,但它们被混淆地称为“references”(我不是在谈论使用的引用类型变量对于 class 类型的变量)。
据我了解,D出于安全原因正式引入了引用,因此可以定义引用的地方仅限于发生接口的地方(函数参数和foreach变量).您不能在代码中自由定义引用。
最好将 D 中的引用解释为常量指针,无论何时使用它们,它们都会自动 间接。与可变指针相比的一大优势是,您不能将它们重新分配给另一个内存区域,而且它们不太可能或实际上永远不会 null
。尽可能使用引用并避免使用指针。唯一的问题:D 的 ref
比 C++ 更受限制,所以如何确保在不将对象移动到堆栈上的情况下传播实际引用?
D 中引用的实际目的是表示输入+输出参数(在函数或 foreach 循环中更新的参数,或者当用作 return 值时由调用者更新的参数)而不是输入或仅输出参数。
请注意,还有一个 inout
关键字,但它具有不同的含义(它是一个代表 const
、immutable
和非常量).
对于仅输出参数,您使用 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 悬挂引用,这样就不太可能意外发生。