管理引用计数的规则

使用引用计数管理对象的生存期允许多个客户端获取和释放对单个对象的访问,而无需相互协调来管理对象的生存期。 只要客户端对象符合某些使用规则,该对象实际上就提供此管理。 这些规则指定如何管理对象之间的引用。 (COM 不指定对象的内部实现,尽管这些规则是对象内策略的合理起点。

从概念上讲,接口指针可以视为驻留在包含接口指针的所有内部计算状态的指针变量中。 这包括内存位置、内部处理器寄存器以及程序员生成的变量和编译器生成的变量。 指针变量的赋值或初始化涉及创建已存在的指针的新副本。 如果某个变量中有一个指针副本(赋值/初始化中使用的值),则现在有两个。 对指针变量的赋值会破坏变量中当前存在的指针副本,变量本身的销毁也一样。 (也就是说,找到变量的范围(如堆栈帧)将被销毁。

从 COM 客户端的角度来看,始终为每个接口执行引用计数。 客户端不应假定对象对所有接口使用相同的计数器。

默认情况是,必须为接口指针的每个新副本调用 AddRef,并且必须针对接口指针的每个销毁调用 Release,除非以下规则允许这样做:

  • 函数的传入参数。 调用方必须在参数上调用 AddRef,因为它将在实现代码中释放(调用 Release),当 out 值存储在其顶部时。
  • 提取全局变量。 从全局变量中指针的现有副本创建接口指针的本地副本时,必须在本地副本上调用 AddRef,因为另一个函数可能会在本地副本仍然有效时销毁全局变量中的副本。
  • 从“薄空气”中合成的新指针。 一个使用特殊内部知识合成接口指针的函数,而不是从其他源获取该指针,必须首先在新合成的指针上调用 AddRef。 此类例程的重要示例包括实例创建例程、QueryInterface的实现等。
  • 检索内部存储指针的副本。 当函数检索由调用的对象在内部存储的指针的副本时,该对象的代码必须在函数返回之前对指针调用 AddRef。 检索指针后,原始对象无法确定其生存期与指针内部存储副本的关系。

默认情况的唯一例外是管理代码知道指向对象上同一接口的指针的两个或更多个副本的生存期的关系,并且只需通过允许对象的引用计数变为零来确保对象不会被销毁。 通常有两种情况,如下所示:

  • 当指针的一个副本已存在并且随后创建第二个副本,然后在第一个副本仍然存在时销毁,则可以省略对 AddRefRelease 的调用。
  • 当指针的一个副本存在并且创建第二个副本,然后在第二个副本之前销毁第一个副本,则可以省略对第二个副本的 AddRefRelease

以下是这些情况的具体示例,前两个尤其常见:

  • 在函数的参数中。 作为参数传递给函数的接口指针的副本的生存期嵌套在用于初始化值的指针中,因此无需对参数使用单独的引用计数。
  • 从函数中传出参数,包括返回值。 若要设置 out 参数,函数必须具有接口指针的稳定副本。 返回时,调用方负责释放指针。 因此,out 参数不需要单独的引用计数。
  • 局部变量。 方法实现控制堆栈帧上分配的每个指针变量的生存期,并可用于确定如何省略冗余 AddRef/Release 对。
  • 后座者。 某些数据结构包含两个对象,每个对象都有指向另一个对象的指针。 如果已知第一个对象的生存期包含第二个对象的生存期,则无需对第二个对象的指针具有引用计数。 通常,避免此周期对于维护适当的解除分配行为非常重要。 但是,由于处理远程处理的作系统部分无法知道此关系,因此,不计其数的指针应非常谨慎。 因此,在几乎所有情况下,让后点器看到第一个指针的“友元”对象(从而避免循环)是首选的解决方案。 例如,COM 的可连接对象体系结构使用此方法。

实现或使用引用计数对象时,应用 人工引用计数很有用,这可以保证函数处理过程中的对象稳定性。 在实现接口的方法时,可以调用有机会将引用计数递减到对象的函数,从而导致对象的过早释放和实现失败。 避免这种情况的可靠方法是在方法实现的开头插入对 AddRef 的调用,并将其与方法返回前 Release 调用配对。

在某些情况下,AddRefRelease 的返回值可能不稳定,不应依赖;它们应仅用于调试或诊断目的。

通过引用计数 管理对象生存期