【问题标题】:What's so special about message passing in smalltalk在 smalltalk 中传递消息有什么特别之处
【发布时间】:2023-03-08 04:41:01
【问题描述】:

我正在介绍 Smalltalk。 在 C++ 中,类中声明的函数可以被该类的对象调用,类似地,在 Smalltalk 中,一个关键字,称为消息,写在对象名称旁边。 (知道的不多,但也想在这里问一下,是否有一个独特的方法来响应一个消息?)

基本上,在我幼稚的头脑中,这似乎只是语法风格上的不同。但是,我想知道在内部编译或内存结构方面,这种调用差异是否有意义。

提前致谢。

P.S:我向你们所有人鞠躬,感谢你们的时间和答案。非常感谢。

【问题讨论】:

  • 不要将关键字与 Smalltalk 中的消息混淆。 Smalltalk 有少量保留关键字(取决于方言,通常为 5 或 6 个),其他所有内容都是在您可以修改的库中从对象发送到对象的消息。甚至像 + 这样的操作符也是发送到数字实例(不是原语,而是对象)的消息,第二个数字作为参数。 Smalltalk 的语法很简单,“对象消息”,结果始终是一个对象(因此您可以向结果发送另一条消息,等等)。有些符号有特殊含义;见wiki.c2.com/?SmalltalkSyntaxInaPostcard :-)

标签: smalltalk


【解决方案1】:

根本区别在于,在 Smalltalk 中,消息的接收者可以完全控制消息的处理方式。它是一个真正的对象,而不是具有对其进行操作的函数的数据结构。

这意味着在 Smalltalk 中您可以向 any 对象发送 any 消息。编译器对此没有任何限制,它都是在运行时处理的。在 C++ 中,您只能调用编译器知道的函数。

此外,Smalltalk 消息只是符号(唯一字符串),而不是 C++ 中内存中的函数地址。这意味着很容易以交互方式或通过网络连接发送消息。有一个perform: 方法可以让您根据其字符串名称发送消息。

一个对象甚至接收到它没有实现的消息。虚拟机检测到这种情况并创建一个Message 对象,然后发送messageNotUnderstood: 消息。同样,如何处理未知消息是对象的唯一责任。大多数对象只是继承引发错误的默认实现,但对象也可以自己处理它。例如,它可以将这些消息转发到远程对象,或将它们记录到文件等。

【讨论】:

  • 所以基本区别在于 Smalltalk 是动态类型(在运行时检测类型错误,允许处理异常)而 C++ 是静态类型(在编译时检测类型错误)?
  • 没有。基本的区别是 Message 在 Smalltalk 中是一个具体化的概念,而在 C++ 中,虚函数分派是隐式的。在 Smalltalk 中,您可以实现一个对象,该对象将接受带有任何类型参数的任何消息(如我回答的最后一段所述)。在 C++ 中没有办法做到这一点。错误处理只是其中一个应用程序,为远程过程调用编写通用代理是另一个,检测代码以进行运行时分析是另一个,等等。
【解决方案2】:

您在 C++ 中调用一个函数,因为在编译期间您知道将调用哪个函数(或者至少您在类层次结构中定义了一组有限的函数。

Smalltalk 是动态类型和后期绑定的,因此在编译期间您不知道要评估哪种方法(如果有的话)。因此,您发送一条消息,并且如果对象具有带有该选择器的方法,则对其进行评估。否则,会引发“消息不理解”异常。

【讨论】:

  • @JayK 我会将此评论“提升”为答案。请你这样做好吗?
  • 我认为除了实现机制之外还有一个微妙的区别:当您考虑消息传递时,您不会考虑“全局上下文所有权”。当您调用一个函数时,该函数现在“拥有世界”。发送消息的感觉不同;您不希望接收消息的对象“接管”。这类似于操作数据结构的程序之间的区别;从概念上讲,任何函数都可以操作任何数据。这种心态会影响设计。我认为更好。
  • @LeandroCaniglia 这样做了。不过,现在对某些读者来说可能太长了。 ;-)
  • @BobNemec 很好。另见 Tim Rentsch (1982) 的 Object Oriented Programming;尤其是消息 pg 下的第三段。 54.
  • "...*在编译期间你知道哪个函数将被调用*...":如果这是真的,C++ 不允许多态性(动态绑定),使它相当无用作为面向对象的语言。
【解决方案3】:

这里已经有了很好的答案。让我添加一些细节(最初,这部分是在评论中)。

在纯 C 中,每个函数调用的目标都是在链接时确定的(使用函数指针时除外)。 C++ 增加了虚函数,调用的实际函数是在运行时确定的(动态调度、后期绑定)。函数指针在某种程度上允许自定义调度机制,但您必须自己编程。

在 Smalltalk 中,所有消息发送都是动态分派的。在 C++ 术语中,这大致意味着:所有成员函数都是虚拟的,并且没有独立的函数(总是有一个接收者)。因此,Smalltalk 编译器从不*决定消息发送将调用哪个方法。相反,调用的方法由实现 Smalltalk 的虚拟机在运行时确定。

实现虚函数调度的一种方法是虚函数表。 Smalltalk 中的近似等价物是方法字典。但是,这些字典是可变的,与典型的虚函数表不同,后者由 C++ 编译器生成并且在运行时不会更改。所有 Smalltalk 行为(BehaviorClass 的超类)都有这样的方法字典。正如@aka.nice 在他的回答中指出的那样,可以查询方法字典。但方法也可以在 Smalltalk 系统运行时添加(或删除)。当 Smalltalk VM 调度消息发送时,它会在接收者的超类链的方法字典中搜索正确的方法。通常有适当的缓存以避免该查找的重复成本。

还请注意,消息传递是对象在 Smalltalk 中进行通信的唯一方式。两个对象不能访问彼此的实例变量,即使它们属于同一个类。在 C++ 中,您可以编写打破这种封装的代码。因此,消息发送在 Smalltalk 中是基础,而在 C++ 中它基本上是一个可选功能。

在 C++、Java 和类似语言中,还有另一种分派形式,称为函数重载。它仅在编译时发生,并根据调用站点上声明的参数类型选择一个函数。你不能在运行时影响它。 Smalltalk 显然不提供这种形式的调度,因为它没有变量的静态类型。尽管如此,它仍然可以使用诸如double dispatch 之类的成语来实现。其他语言,例如 Common Lisp 的 CLOS 或 Groovy,提供了更通用的 multiple dispatch,这意味着将根据接收者的类型和 runtime 来选择方法所有参数的类型。

* 一些特殊的消息如ifTrue:ifFalse:whileTrue:通常直接编译成条件分支并在字节码中跳转,而不是消息发送。但在大多数情况下,它不会影响语义。

【讨论】:

    【解决方案4】:

    这里有一些你在 C++ 中找不到的例子

    在 Smalltalk 中,您通过发送消息(根据方言发送到超类或命名空间)来创建一个新类。

    在 Smalltalk 中,您通过向编译器发送消息来编译新方法。

    在 Smalltalk 中,通过发送消息来打开调试器以响应未处理的异常。所有的异常处理都是通过发送消息来实现的。

    在 Smalltalk 中,您可以查询类的方法,或通过发送消息收集其所有实例。

    更简单地说,所有控制结构(分支、循环等)都是通过发送消息来执行的。

    一直是消息。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-02-22
      • 2021-01-29
      • 2011-04-18
      • 2017-02-19
      • 2016-09-30
      • 2011-03-21
      • 2011-07-13
      相关资源
      最近更新 更多