Ada中匿名访问对象隐式转换为何被禁止?栈运算符报错解析
让我来帮你拆解这个Ada类型系统的细节问题——你遇到的错误本质是匿名访问类型和命名访问类型之间的转换限制,以及Ada对运算符查找的规则在起作用。
核心问题:两种访问类型的不匹配
先明确代码里的类型关系:
- 你定义的
Stack是命名访问类型:type Stack is access all Cell; - 而
SS和TT的类型是匿名访问类型:SS: access Cell := S;(这里access Cell没有显式命名,属于匿名访问类型)
Ada的类型系统对这两种访问类型的隐式转换有严格的安全限制,这就是错误的根源。
1. 为什么return SS = TT;会报错?
当你写SS = TT时,编译器会优先查找当前包中定义的=运算符——也就是你为Stack类型写的那个function "="(S, T: Stack) return Boolean;。为了匹配这个运算符,编译器会尝试把SS和TT(匿名access Cell)隐式转换成Stack类型。
但Ada的规则明确禁止这种转换:独立的匿名访问对象不能被隐式转换为命名访问类型。这是因为匿名访问类型的生命周期管理和命名访问类型不同,隐式转换可能导致悬空指针或内存管理问题,所以编译器直接拒绝了这种操作,给出你看到的错误提示。
2. 为什么return not (SS /= TT);可以正常编译?
这里的关键是:SS /= TT用的是Ada预定义的匿名访问类型不等运算符,而不是你定义的Stack类型的运算符。
因为你没有为匿名的access Cell类型定义自己的/=,编译器会直接使用语言内置的版本——这个预定义运算符可以直接比较两个同类型的匿名访问对象,不需要任何类型转换,它会判断两个对象是否指向不同的存储单元(或者是否一个是null另一个不是)。取反之后,正好实现了你原本要判断“两个栈结构完全一致(同时到达null)”的需求。
3. 为什么自动生成的/=表现不同?
John Barnes提到的编译器自动生成/=,是针对你定义了=的类型(也就是Stack类型)。自动生成的代码大概是这样的:
function "/="(S, T : Stack) return Boolean is begin return not (S = T); end "/=";
这个自动生成的函数会直接调用你写的Stack类型的=运算符——而你的=运算符本身存在那个隐式转换的错误,所以调用这个自动生成的/=时自然会触发同样的问题。
但注意:自动生成的/=是针对Stack类型的,和你代码里SS /= TT用的匿名访问类型的/=完全是两个不同的运算符,所以后者不会有转换问题。
更优雅的修复方案
除了用not (SS /= TT),你还有两种更清晰的修复方式:
- 显式转换:直接把匿名访问类型转换成
Stack类型,编译器允许显式转换(因为你明确承担了风险):return Stack(SS) = Stack(TT); - 统一类型声明:把
SS和TT直接声明为Stack类型,这样后续的相等性判断就不需要任何转换了:
这样SS : Stack := S; TT : Stack := T;SS = TT会直接调用你定义的Stack类型的=运算符,完全符合你的预期。
内容的提问来源于stack exchange,提问作者TamaMcGlinn

