【LabVIEW面向对象闲谈】类:传值还是传引用

在这篇文章中,我们将探讨另一个重要话题:按值类和按引用类。

按值类

副值类遵循数据流范式。在LabVIEW中,类是按值原生的,因此表现类似于其他基本LabVIEW数据类型(不包括refnums):数字、数组、字符串、簇等。
在LabVIEW中,数据由线路“承载”。当线路分叉时,线路中的数据会被复制。因此,这两种传输的数据路径完全独立。换句话说,对第一根线的数据进行的操作对第二根线的数据没有影响,反之亦然。这种行为称为“按值”,遵循LabVIEW本身的数据流规则。
由于LabVIEW中的类是按值的,当我们实例化一个对象时,它遵循上述规则。如果承载该对象的线路分叉,就会生成该对象的内存副本,就像有两个独立的对象一样。对第一个对象施加的方法和设定器对第二个对象没有影响,反之亦然。

旁引用类

副引用类的行为类似于refnum数据类型。refnum 是对内存中某个位置的引用,可以通过某些保护机制从多个“点”访问。LabVIEW 中的 refnum 类型包括队列、文件输入输出、控制引用、VI 服务器类型等。
由于 refnum 是指向内存区域的指针,当携带 refnum 的线路分叉时,会复制 refnum 本身,但不会复制它所指向的数据。指针遵循按值编程的规则,但不遵循它所指向的内存区域(因此也就是数据)。
旁引用类遵循这种方法。这种类利用了类似refnum的数据结构,因此继承了它们的行为(在下一篇文章中,我们将详细介绍by-reference类是如何实现的)。
在旁引用对象中,其数据存储在内存区域中,该区域有可能在并行代码段之间共享。例如,考虑两个访问同一个旁引用对象的 while 循环。虽然两个循环之间没有数据流依赖,但对一个循环中共享对象的更改会影响另一个循环。
在像 C# 这样的面向对象编程语言中,类仅是通过引用的。方法或函数对对象执行的操作会修改对象本身,并且总是指向原始对象。要复制一个对象,需要以原始对象为起点克隆一个新的对象。

优缺点

现在让我们来分析这两种职业的优缺点,以便做出更明智的决策。

副值类具有以下优势:

  • 使用数据流的概念是LabVIEW编程中完全自然的过程。使用按值类编程与在LabVIEW中使用数字、字符串及其他基本数据类型(不包括refnums)无异。

  • 由于 by-value 对象不会在代码的不同部分之间“共享”,因此除了在承载它的线路中外,代码中不能更改它。这使得调试变得快速且简单。

  • 使用值类使多个线程能够运行,无需担心某一代码段会影响另一段的值。

  • 按值对象遵循LabVIEW环境中的自然方法,简化了开发人员之间的代码共享,尤其是当他们拥有不同专业水平时。

次值类存在以下缺点:

  • 同一对象不能跨多个分支共享。
    假设我们有一个 ReportSettings 对象,包含报表创建设置,并被多个并行运行的 while 循环使用。一种可能的方法是将 ReportSettings 对象(按值)传递给使用该对象的循环。每个循环中对象都会有多个独立的副本。然而,这可能不是好做法,因为如果你必须对对象做更改(例如调用其设置器或改变状态的方法),每个“线”都有一个独立对象,每次更改只会影响该对象对相关“线”的复制。在这种情况下,如果你想要一个单一对象,正确的做法是有一个单一的 actor(最简单的是 while 循环),其中存在 ReportTSettings 对象,并提供一个机制,用于该角色与所有需要对该对象执行操作的其他角色之间交换数据或命令。

  • 处理大型对象时,内存管理可能会出现潜在问题。如果一个对象包含大量数据,占用了应用内存的相当大一部分,就必须确保它不会被频繁复制。内存管理和LabVIEW创建的副本应始终受到控制。这同样适用于大型的次值对象。
    假设我们有一个TestResult对象,包含一个测试序列的结果,可能占用数十兆或数百兆字节的数据。在这种情况下,每次分支托管 TestResult 对象的线路时,都会创建一个副本,迅速提高应用的 RAM 使用率。

旁引用类具有以下优势:

  • 可以轻松地在多个并行代码段中共享同一对象(例如两个并行的while循环)。使用共享的by-reference对象时,某一点的更改在其他所有点上都是“可见”的。例如,考虑两个共享同一个by-reference对象的并行循环,如果第一个循环通过调用相关的setter或方法修改了对象,更新后的对象也会被第二个循环看到。

  • 可能的代码简化。再假设我们有一个 ReportSettings 对象,包含报表创建设置,并且在应用中多个并行 while 循环中以只读模式使用。by-reference方法允许读取其状态的各种循环始终保持对象的最新版本,而无需实现除将对象从by-value变为by-reference的特殊机制外。

  • 处理大型对象时,内存管理更为高效。按引用方法避免了同一对象不必要的内存复制。数据的复制不仅发生在线路分叉时,也发生在数据传递到子VI系统时。可以观察到LabVIEW直接从LabVIEW工具 ->配置文件->显示缓冲区分配中进行数据复制。处理大型对象时,采用按引用方法可能更合适,以避免不必要的内存复制增加应用RAM使用量。一个实际例子是IMAMAQ库,其中图像(可能占用大量内存)通过引用管理。

而旁称类则有以下缺点:

  • 它们需要实施保护机制。而采用旁引用方法,实际上存在共享资源,这些机制避免了竞态条件或其他不良条件。

  • 数据流范式的中断,而数据流范式是LabVIEW环境中自然使用的编程方法。

  • 调试更复杂。使用引用类进行代码调试可能既耗时又复杂。

  • LabVIEW 不原生支持引用类的实现。因此,由于实现使本值类为 by-value 的类别,实现了通过引用的机制,存在开销。

结论

在这篇文章中,我们探讨了按价值和按引用类别,它们的主要区别、优缺点。自然而然地会出现的问题是:我们应用中的类应该按值分类还是引用分类?一如既往,答案是…这要看情况。这取决于应用的架构、类之间的相互作用、程序员的经验和习惯,以及程序员想要执行的任何内存管理优化。根据我的经验,第一选择总是偏向按价值类别。然而,当代引用类能够实现代码的有意义简化,并且需要处理大型对象时,使用by-reference类是合适的。