我想在Crystal中定义一个通用的内存包装器。我有以下晶体代码:
module Scalar(T)
abstract def value: T
end
class ScSticky(T)
include Scalar(T)
def initialize(sc : Scalar(T))
@sc = sc
@val = uninitialized T
end
def value: T
@val ||= @sc.value
end
end
换句话说,我希望ScSticky
只调用底层Scalar(T)
一次,并为所有后续调用返回缓存的输出。然而,如果T
是Int32
,则上述方法不起作用
例如,包装此类时
class ScCounter
include Scalar(Int32)
def initialize
@val = 100
end
def value: Int32
@val += 1
@val
end
end
ScSticky(ScCounter.new).value
将始终等于0
(正如我所理解的,因为unitialized Int32
实际上是用0值初始化的(
我非常感谢对这个问题的帮助
Upd:实现这一点的正确方法似乎是使用nil
,但我在理解这种实现的确切外观方面存在问题。我还希望能够记住.value
方法,即使它返回nil
(换句话说,如果T
是可幂类型(
您正在使用不安全的功能"CCD_ 12";,这意味着;保留之前记忆中的任何内容";(理论上,这个值是随机的,可能是无效的,但在实践中,你通常最终会得到0——但它仍然不能保证(
关于uninitialized
功能的短篇故事是请永远不要使用它。
如果你写了@val = 0
,这种行为不会让你感到惊讶——你就是这么写的。
您必须定义@val : T? = nil
-以使其为幂零(具有单独的可能值nil
,它是自己的类型-Nil
(
您可能认为unitialized
将nil
带入画面,但事实并非如此。
为了回应您关于将nil
也包含在可能值中的评论,这里有一个完整的解决方案,它使用唯一的";哨兵";结构,用户永远无法创建。
module Scalar(T)
abstract def value: T
end
private struct Sentinel
end
class ScSticky(T)
include Scalar(T)
@val : T | Sentinel = Sentinel.new
def initialize(@sc : Scalar(T))
end
def value: T
val = @val
if val.is_a?(Sentinel)
@val = @sc.value
else
val
end
end
end
class ScCounter
include Scalar(Int32)
def initialize
@val = 100
end
def value: Int32
@val += 1
end
end
sc = ScSticky.new(ScCounter.new)
p! sc.value #=> 101
p! sc.value #=> 101