引用计数

引用计数类型

引用计数是一种垃圾回收机制,其基于向数据类型或任何内存区域分配计数器;当获取该数据类型的新引用时,计数器会增加;当释放引用时,计数器会减小。一旦释放了最后引用,与该数据类型关联的资源即可释放。

GLib 在许多数据类型中使用引用计数,并提供了 `grefcount` 和 `gatomicrefcount` 类型,以便在新数据类型中实现安全和原子的引用计数语义。

GLib 中的 `grefcount` 和 `gatomicrefcount` 会被当做完全不透明的类型,因此你需要始终使用提供的API 来增加和减少计数器,并且绝不直接检查其内容,也不将其内容与其他值进行比较。

引用计数数据

“引用计数盒”或“RcBox”是一个不透明包装数据类型,可以保证它和给定数据类型大小一致,并且扩展了给定数据类型的引用计数语义以进行内存管理。

假设你有像放在栈中的结构之类的普通旧数据类型,并且希望提供额外的API在堆中使用它;或者如果你想实现一种新类型,以便通过引用传递,而不必实现自己的引用计数或复制/释放语义,则RcBox 很管用。

典型用法是

typedef struct {
  char *name;
  char *address;
  char *city;
  char *state;
  int age;
} Person;

Person *
person_new (void)
{
  return g_rc_box_new0 (Person);
}

每当你希望获取内存上的引用时,都应该调用 `g_rc_box_acquire()`;类似地,当你希望释放引用时,你应该调用 `g_rc_box_release()`

// Add a Person to the Database; the Database acquires ownership
// of the Person instance
void
add_person_to_database (Database *db, Person *p)
{
  db->persons = g_list_prepend (db->persons, g_rc_box_acquire (p));
}

// Removes a Person from the Database; the reference acquired by
// add_person_to_database() is released here
void
remove_person_from_database (Database *db, Person *p)
{
  db->persons = g_list_remove (db->persons, p);
  g_rc_box_release (p);
}

如果你在结构内部分配了额外的内存,则可以使用 `g_rc_box_release_full()`,它提供了一个函数指针,如果释放的引用是最后一个引用,则将调用该函数指针。

void
person_clear (Person *p)
{
  g_free (p->name);
  g_free (p->address);
  g_free (p->city);
  g_free (p->state);
}

void
remove_person_from_database (Database *db, Person *p)
{
  db->persons = g_list_remove (db->persons, p);
  g_rc_box_release_full (p, (GDestroyNotify) person_clear);
}

如果你想要转移引用计数数据类型的所有权而不增加引用计数,则可以使用 `g_steal_pointer()`

  Person *p = g_rc_box_new (Person);

  // fill_person_details() is defined elsewhere
  fill_person_details (p);

  // add_person_to_database_no_ref() is defined elsewhere; it adds
  // a Person to the Database without taking a reference
  add_person_to_database_no_ref (db, g_steal_pointer (&p));

线程安全

对使用 `g_rc_box_alloc()`, `g_rc_box_new()`, 和 `g_rc_box_dup()` 分配的数据的引用计数操作不是线程安全的;你的代码应负责确保在同一线程上获取和释放引用。

如果你需要线程安全的引用计数,你应该使用 `g_atomic_rc_*` API

操作 原子等价
g_rc_box_alloc() g_atomic_rc_box_alloc()
g_rc_box_new() g_atomic_rc_box_new()
g_rc_box_dup() g_atomic_rc_box_dup()
g_rc_box_acquire() g_atomic_rc_box_acquire()
g_rc_box_release() g_atomic_rc_box_release()
g_rc_box_release_full() g_atomic_rc_box_release_full()

对使用 `g_atomic_rc_box_alloc()`, `g_atomic_rc_box_new()`, 和 `g_atomic_rc_box_dup()` 分配的数据的引用计数操作保证为原子操作,因此不同线程可以安全地执行它。必须注意,只有引用获取和释放是原子的;数据的内客容更改由你负责。

混合原子和非原子引用计数操作是一个编程错误。

自动指针清理

如果您想通过引用计数向普通旧数据类型添加 g_autoptr() 支持,则可以使用 G_DEFINE_AUTOPTR_CLEANUP_FUNC()g_rc_box_release()

G_DEFINE_AUTOPTR_CLEANUP_FUNC (MyDataStruct, g_rc_box_release)

如果您需要清除数据的内容,您需要使用一个辅助函数来调用 g_rc_box_release_full()

static void
my_data_struct_release (MyDataStruct *data)
{
  // my_data_struct_clear() is defined elsewhere
  g_rc_box_release_full (data, (GDestroyNotify) my_data_struct_clear);
}

G_DEFINE_AUTOPTR_CLEANUP_FUNC (MyDataStruct, my_data_struct_release)

g_rc_box*g_atomic_rc_box* API 在 GLib 2.58 中引入。