Common Lisp中struct和class的区别

14

我对结构体的理解是,它有用于存储数据的插槽,有一个类型,有make-X和插槽访问函数,并且可以通过方法进行专业化(因为它有一个类型)。

我对类的理解是,它具有相同的功能和多重继承。this question的最佳答案指出,结构体可以具有单一继承,并且CLOS的初始实现比结构体“慢得多”。

基于人们谈论CLOS和结构体的方式,我认为必须存在其他差异,但是我的琐碎的谷歌搜索没有结果。因此我问:CLOS和结构体之间的实际区别是什么?

2个回答

27

结构体

结构体更为原始。它们在编程语言中通常被称为“记录”。它们在类之前就已经在Common Lisp中引入了。1984年的CLtL1(描述Common Lisp的第一本书)已经有了结构体,后来又添加了一个名为CLOS的标准对象系统。结构体提供了以下功能:

  • 简洁的定义宏DEFSTRUCT
  • 单继承
  • 快速的槽访问
  • 为槽定义读取和设置函数
  • 定义类型谓词
  • 定义构造函数
  • 定义复制函数
  • 可打印表示:结构体可以被读取和打印
  • 上述函数可能会被内联

有用的附加功能:

  • DEFSTRUCT可以定义基于列表和向量的结构体表示,除了结构体类型之外

限制:

  • 在更改结构类型后,结构实例不会被更新。
  • 如果更改了结构类型,最好重新编译和运行更改的代码。也许需要重新启动程序。重新定义结构的效果在标准的Common Lisp中是未定义的。
  • 非常少的内省功能:便携式Common Lisp不能以简单的方式告诉我一个结构的超类/子类或者插槽。
  • 默认情况下无法通过插槽名称访问插槽。

扩展功能

  • 一些实现提供更多的运行时内省功能,并在某些CLOS函数中进行了一些集成。

CLOS类

CLOS是在80年代中期/晚期基于两个早期的对象系统(Flavors和LOOPS)发明的。它提供了:

  • 一个定义宏DEFCLASS
  • 多重继承
  • 用于创建、初始化等的协议
  • 基于类的变化(新槽位、重新定义的槽位、删除的槽位、继承关系的变化等),CLOS对象可以在运行时进行更改和更新
  • CLOS对象可以在运行时更改其类并进行更新
  • 可以通过槽位名称进行访问

限制:

  • 没有默认的打印机/读取器
  • DEFCLASS定义不够简洁

扩展:

  • 通过添加特定实现功能来加快槽位访问速度
  • 元对象协议提供了额外的功能和灵活性:内省和反射。有时只提供MOP的部分功能。
  • 用户提供的扩展可用,特别是对于支持MOP的实现

Common Lisp

在某些情况下,Common Lisp标准没有规定如何实现某个功能:结构体、类,或者甚至其他东西。流和条件就是例子。如果一个Common Lisp实现使用CLOS来实现这些功能,通常是为了增加灵活性的好迹象。


出于好奇,如何测试一个实现是否使用 CLOS 来处理(例如)流? - Reepca
3
如果一个类型是standard-object的子类型,那么它就是一个CLOS类。对于一个条件类型:(subtypep 'error 'standard-object) -> T或NIL。一些实现可能允许使用各种CLOS和非CLOS类型的流。 - Rainer Joswig
你能详细说明一下在运行时更改类是如何有用的吗?我想不出其他语言真正具备这个功能,所以从来没有考虑过这些方面。 - MasterMastic
2
@MasterMastic:更改是指将对象更改为不同类的实例吗?a)考虑任何您想要更新的长时间运行的内容:无论是在开发环境还是应用程序中。您可能有一个Lisp应用程序,希望加载软件补丁而不终止应用程序。b)一个典型的简单事情是一旦应用程序了解更多信息就将对象更改为更特殊的类。哦,TCP流实际上是POP3流...将流实例更改为pop3流类。 - Rainer Joswig
哦,我明白了。我在谈论更改类的事情。至于后者,这对于任何语言来说都很常见,不是吗?例如TCP -> POP3函数。至于前者,我认为我会犹豫是否要修补类,这可能对代码库不太好,对吧?但我敢打赌这是另一个讨论话题。非常感谢! - MasterMastic

4

defstruct在幕后为您完成更多工作,例如:

  1. 自动定义插槽访问器
  2. 定义可读的print-object方法

此外,结构插槽访问速度更快(尽管差异可能微不足道)。

总之,除非您需要MOPish功能,否则可以使用defsrtuct


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接