TclOO:将变量声明为 "class" 级别或在构造函数中的区别

TclOO: Difference between declaring variable as "class" level or in constructor

我对在哪里使用 variable myVar 以及何时需要使用 my variable myVar

感到困惑

如果我这样定义一个class,就没有错误:

oo::class create Foo {
    variable bar

    constructor {input} {
        set bar $input
    }

    method twoBar {} {
        return [expr {2 * $bar}]
    }
}

然后

% set f [Foo new 42]
::oo::Obj12
% $f twoBar
84

但是如果我将 variable bar 移动到构造函数中:

oo::class create Foo {
    constructor {input} {
        variable bar
        set bar $input
    }

    method twoBar {} {
        return [expr {2 * $bar}]
    }
}

然后

% set f [Foo new 42]
::oo::Obj12
% $f twoBar
can't read "bar": no such variable

这可以通过在 twoBar 方法中添加 my variable bar 来解决。

怎么回事?

在方法内部,variable只是您所知道的常规Tcl命令,当前命名空间是实例命名空间(每个对象都有)。

在声明级别,variable是不同的。(完全是不同的命令。)它配置了变量解析器 为配置的实体(示例中的 class Foo)设置一个变量名,如果在本地(方法)范围内找不到,将在实例命名空间中查找;解析器实际上会对变量名列表执行此操作。变量解析器适用于 class 定义的所有方法——但 not 它的 subclasses 或 superclasses (我在另一个上试过方式;它糟透了)——或者实例如果在实例定义的上下文中。

在您的示例中,这意味着:

  1. 在第一种情况下,构造函数和 twoBar 方法都有 bar 实际上引用变量 ::oo::Obj12::bar (在 TclOO 实现中关于命名空间名称的通常假设下) .
  2. 在第二种情况下,虽然bar在构造函数中引用::oo::Obj12::bar(从技术上讲,仅在调用variable之后),在twoBar方法中bar 仅引用您从未设置的 local 变量。这意味着从 bar 读取失败(因为变量读取的标准语义)。

在该方法中调用 my variable barvariable bar 具有相同的效果。不同的是,一个可以提升为可从对象外部调用,另一个可以一次性绑定和设置多个变量;两者都是不常见的用途,但根本不同。


在字节码级别,两个 twoBar 方法之间唯一明显的区别是(在第一个版本中):

% tcl::unsupported::disassemble method Foo twoBar
ByteCode 0x0x7ff5a2845810, refCt 1, epoch 17, interp 0x0x7ff5a2808010 (epoch 17)
  Source "\n        return [expr {2 * $bar}]\n  ..."
  Cmds 2, src 38, inst 6, litObjs 1, aux 0, stkDepth 2, code/src 0.00
  Proc 0x0x7ff5a2835f10, refCt 1, args 0, compiled locals 1
      slot 0, scalar, resolved, "bar"
  Commands 2:
      1: pc 0-5, src 9-32        2: pc 0-4, src 17-31
  Command 1: "return [expr {2 * $bar}]..."
  Command 2: "expr {2 * $bar}..."
    (0) push1 0     # "2"
    (2) loadScalar1 %v0     # var "bar"
    (4) mult 
    (5) done 

变量bar(在插槽 0 中)被标记为“resolved”(它在幕后进行恶作剧)。它们在其他方面完全相同。构造函数在命令序列上有很大差异,因此比较起来很棘手,但我们仍然可以看到那里发生了什么。

% tcl::unsupported::disassemble constructor Foo
ByteCode 0x0x7ff5a2845c10, refCt 1, epoch 17, interp 0x0x7ff5a2808010 (epoch 17)
  Source "\n        set bar $input\n  ..."
  Cmds 1, src 28, inst 5, litObjs 0, aux 0, stkDepth 1, code/src 0.00
  Proc 0x0x7ff5a2835d90, refCt 1, args 1, compiled locals 2
      slot 0, scalar, arg, "input"
      slot 1, scalar, resolved, "bar"
  Commands 1:
      1: pc 0-3, src 9-22
  Command 1: "set bar $input..."
    (0) loadScalar1 %v0     # var "input"
    (2) storeScalar1 %v1    # var "bar"
    (4) done 

同样,barresolved