在之前讨论中,我们看到了按值类和按引用类的区别、优缺点。如帖子所示,G码类是按值的。而旁比类则需要实现特定的机制或数据结构。在这篇文章和下一篇文章中,我们将概述获取传引用类的主要方法。因此,在第一篇文章中,我们将看到该类外部的机制,以便通过引用来使用它。另一方面,在第二篇文章中,我们将看到类内部机制将其转化为“内在”的参照类。
1. 变量
实现传引用类最简单直接的方法是使用变量(本地或全局)。简而言之,我们拿一个对象,创建一个变量来访问它以便读写。这样,物体就摆脱了承载线,成为一个旁参考物体。
变量的使用可能导致竞争条件,因此不建议使用它们。这并不意味着变量绝不能使用。有时变量有助于保持代码的简洁,避免过于复杂的共享机制,而这些机制在某些情况下可能并非必需。
例如,变量在使用多次写入(WORM)方法时是可以的。在这种情况下,变量只写入一次,之后每次后续访问都会被读取。在这种情况下,不存在竞争条件。
通过局部变量访问对象。
使用局部变量进行对象的引用访问的代码示例。
2. 功能全局变量(FGV)
FGV是一种非重录虚拟智能,利用未初始化的移位寄存器来存储任何类型的数据(包括对象)。FGV的目的是在FGV自身连续呼叫之间存储数据。最简单的形式是FGV存储数据。然而,用它来封装比单纯写入和读取数据更复杂的操作更有效。
FGV通常伴随一个输入(枚举或字符串),指示要执行的操作。为了进一步探讨这个话题,这里有几个有用的链接:什么是功能全局变量?以及功能全局变量(FGV)。
让我们回到面向对象编程。可以在FGV中存储一个对象,使该对象本身成为参考。通过将对象封装在FGV中,该对象被存储在内存中(即实现FGV本身的VI中),从而成为一个准指对象。
为了避免竞态条件,使用FGV不仅存储对象,还将特定操作委托给对象,即直接调用FGV内部的类方法。让我们来考虑一个实现简单计数器的类。类的状态是一个数字,表示计数的当前值。此外,类还有 Increment 方法,递增计数器的当前值(类状态)。如果我们用一个只存储对象的FGV,要调用增量方法,我们必须先读取FGV中的存储值,调用增量方法,然后再更新FGV中的值。然而,这种方法会产生竞争条件。为避免这种情况,应通过将增量方法的执行委托给FGV来插入额外的逻辑。在这种情况下,对对象的操作是在FGV内部进行的,从而保护我们免受竞态条件的影响。
使用FGV时,对象成为共享资源,其访问由FGV本身中介。此外,FGV作为非重录VI,提供了一种保护机制,保证一次访问一次。具体来说,如果两部分并行代码同时尝试访问FGV(也就是所含对象),而一个请求被满足,另一个请求则等待完成后再被送达。
下图展示了封装 DeviceConfiguration 对象的 FGV 示例。示例中的FGV封装了DeviceConfiguration对象的SampleRate属性的初始化、加载和读写方法。
FGV 包含 DeviceConfiguration 对象的示例。
使用FGV进行对象的引用访问的代码示例。示例执行变量段落中给出的操作相同。
与变量不同,FGV如果实现得当,可以保护我们免受竞赛条件的影响。相比之下,使用FGV存在以下缺点:
- 扩展性有限。如果我们需要向FGV存储的对象添加新的公共方法,就需要修改FGV本身的代码。如果还需要通过引用使用多个类,就必须为每个类提供一个FGV(即带有相关代码的专用VI),这可能会带来麻烦。
- FGV连接器面板上的一堆端子乱成一团。FGV可以封装其所存储对象的多种方法,这些方法可能需要不同类型的输入和输出。这些输入和输出必须暴露在物体与外界的接口中,然后在FGV的连接器面板中报告。这会使连接器面板变得复杂,输入输出众多,使FGV难以使用。
3. 数据值参考(DVR)
DVR是一种共享内存位置。携带DVR的线传输的是数据的内存参考,而非数据本身。DVR需要使用原位结构来读写访问,防止其存储数据的内存副本。DVR在进程写入时会自动锁定其他写入器,并允许多次并行读取。
通过使用DVR制作副参考对象非常简单。你只需创建一个你想通过引用创建的类别类型的DVR。完成后,你通过“置置”结构访问该对象。
为 DeviceConfiguration 对象创建 DVR。
使用DVR通过引用创建班级有以下优势:
- 保护机制。DVR为数据(在我们这里是对象)的并发访问提供了保护机制,保证每个“写入者”都能独占访问。具体来说,如果两部分并行代码同时尝试获得DVR写入权限(也就是对所含对象的访问),而一个请求被满足,另一个请求则等待完成后再被送达。
- 完全可扩展性。DVR是在运行时创建的,而且非常简单。除了占用的内存量外,我们能创建的DVR数量没有限制。
- 提升了内存管理效率。访问DVR中的数据需要使用“就地”结构,以避免创建存储数据的内存副本。
- DVR与物体的集成非常出色。使用专用DVR携带类的对象,可以在不使用原地结构的情况下访问该类的属性节点。这是NI提供的辅助工具,用来让代码更清晰。实际上,DVR的访问方式和使用经典方法是一样的。
直接使用DVR类别的属性节点。
- 成熟的技术。DVR已经存在并使用了多年,因此可以被视为一种可靠的工具。
使用DVR时唯一的缺点是使用了“原地”结构,其奇怪的错误处理方式让代码看起来很难看。
使用DVR进行对象的引用访问的代码示例。示例执行的操作与变量和FGV部分相同的。
4. 单元素队列(SEQ)
SEQ是一个被强制只能有一个元素的队列。因此,我们可以将队列引用视为对其所包含数据的引用。SEQ利用队列的锁(Dequeue)和解锁(Enqueue)功能,确保对数据的独占访问。
在功能上,SEQ类似于DVR。然而,自DVR出现以来,鉴于其更高效的内存管理和与类的良好集成,趋势是更倾向于DVR配备指向内存区域的指针。
与DVR一样,通过SEQ渲染传引用对象非常简单。你只需将想要通过引用的对象放在长度为1的队列中即可。完成后,你通过“Dequeue”和“Enqueue”函数访问该对象(见下图)。
创建携带 DeviceConfiguration 对象的 SEQ。
Extraction of object from SEQ.
Some notes on the examples in the figure above:
- The queue must be forced to have 1 element.
- In data extraction (Dequeue) the timeout must be -1. This causes any other concurrent operations to wait for the current operation to be completed.
- The error wire is intentionally not connected to the Dequeue function. A more effective error handling can certainly be thought of, but it is essential that the re-entry of the data into the queue is always guaranteed.
Using SEQs to make a class by-reference has the following advantages:
- 保护机制。SEQ为数据(在我们这里是对象)的并发访问提供了保护机制,保证每个“写入者”都能获得受限访问。具体来说,如果两部分并行代码同时尝试获得对队列的写权限(也就是对包含对象的访问),而其中一个请求被满足,另一个则等待完成后才能被响应。
- 完全可扩展性。SEQ在运行时创建,且方式简单明了。除了占用内存的数量外,我们能创建的SEQ数量没有限制。
- 能够通过名称检索队列。由于它是队列,SEQ引用也可以按名称创建。这使得队列引用可以在代码的任意位置创建,而无需携带相对线路。我们可以认为这是一个优势,因为它使得SEQ可以在代码的不同节点轻松调用。同时,必须说明这会使得确定对象被修改的位置变得更加困难,使得调试变得更加复杂。
SEQ的主要缺点是需要采取措施以确保其运行(超时至-1、错误处理、顺序去队列和重新排队),这些完全由开发者负责。未能做到这一点会对整个申请产生严重负面影响。如果你取消了队列但不取消后续的重新排队,那么对SEQ的访问将无法恢复。
使用SEQ进行对象的引用访问的代码示例。示例执行变量部分示例中给出的操作相同,FGV和DVR。
结论(第一部分)
在这篇文章中,我们展示了多种在引用模式中使用类的实现方法。帖子中介绍的技术(变量、FGV、DVR和SEQ)都是本类之外的机制。通过这些方法,类别仍然是按价值计算的。允许通过引用使用的是该类外部的代码。
在下一篇文章中,我们将探讨将该类内部化为传引用类的技术。
所见的方法针对不同情境和情境进行了调整。变量无疑是使用速度最快的(代码实现速度快),但同时它们会给出竞态条件。变量在蠕虫法中是可以的。
FGV取代变量是因为它们避免了竞态条件,并且还可用于封装逻辑。请记住,FGV是必须在编辑时创建的VI,因此它们不可扩展。
最后,DVR和SEQ是实现副参考对象的极佳工具。它们都易于使用,具有出色的可扩展性,从功能角度看,它们是等效的工具。鉴于更高效的内存管理和与课程的良好集成,DVR值得优先考虑。










