Reference Counting

Reference Counts

To manage the lifetime of objects, Panda3D has a reference counting system for many objects. This means that for every object that uses this mechanism, a reference count is kept which counts the number of references exist to that object. Every time a new reference is made (eg. assigned to a new variable), the reference count is increased. When the reference count reaches zero, the object is deleted.

This is similar to Python’s reference counting system, and in fact, the two systems interact when Panda3D is used with Python. However, since an object’s lifetime may persist beyond the lifetime of an object in Python, Python’s own reference counting system alone is not sufficient.

The class that manages the reference count is ReferenceCount. To see if a class is reference counted, check if it inherits from ReferenceCount. C++ classes that are reference counted inherit from either ReferenceCount or TypedReferenceCount (if use of the typing system is desired), or another class that itself inherits from ReferenceCount.

Managing Reference Counts

There are several ways that the reference count can be manipulated in code. To get the number of references to an object, use the get_ref_count() method.

The reference counted can be incremented and decremented manually using the ref() and unref() methods, but be careful! This messes up Panda’s internal bookkeeping, and will likely cause crashes and memory leaks. Do not do this unless you know exactly what you’re doing!

Smart Pointers

To correctly track references in C++ code, Panda3D needs to know whenever a new reference to the class is created. Therefore, Panda3D defines a template class PointerTo<T> which is just like the ordinary pointer T*, except that the reference count is incremented when it is created or assigned, and decremented when it goes out of scope. There is a convenience macro PT(T) to save typing.

There is also a macro ConstPointerTo<T>, shortened to CPT(T), which manages a pointer to a const object. This is similar to const T* in C++; the pointer can still be reassigned, but the object may not be modified.

This is a usage example:

PT(TextNode) node = new TextNode("title");

node->set_text("I am a reference counted TextNode!");

A PointerTo is functionally equivalent to a regular pointer, and it can cast implicitly to the appropriate pointer type. You can use ptr.p() to explicitly retrieve the underlying plain pointer.

When they aren’t necessary

Although it is safest to use PT(T) to refer to an object in all cases, in some cases it is not strictly necessary and may be more efficient not to.

This can only be done, however, when you are absolutely sure that the reference count cannot decrease to zero during the time you might be using that reference. In particular, a getter or setter of a class storing a PointerTo need not take or return a PointerTo since the class object itself already holds a reference count.

The following code example highlights a case where it is not necessary:

PT(TextNode) node;
node = new TextNode("title");

use_text_node(node);

void use_text_node(TextNode *node) {
  node->do_something();
}

One crucial example where the return value of a function has to be a PointerTo is where the function may return a new instance of the object:

PT(TextNode) make_text_node() {
  return new TextNode("title");
}

PT(TextNode) node = make_text_node();

Managing Reference Count

Although it is recommended to use PointerTo for all references, it is possible to manage the reference count manually using the ref() and unref() methods, as already stated above.

This can not always work as an alternative, though, since an object returned from a function that returns a PointerTo may be destructed before you get a chance to call ref() to save it! This is why it’s recommended to always use PointerTo except in very rare, low-level cases.

Warning

The unref() method should not be used if it may cause the reference count to reach zero. This is because a member function cannot destruct the object it is called on, so this will leak memory. Instead, you should use the unref_delete(pt) macro to decrease the reference count unless you are absolutely sure that it will not reach zero.

Weak Pointer

A weak pointer stores a reference to an object without incrementing its reference count. In this respect it is just like a regular C++ pointer, except that weak pointers have extra advantages: they can know when the underlying object has been destructed.

Weak pointers are implemented by WeakPointerTo<T> and WeakConstPointerTo<T>, abbreviated to WPT(T) and WCPT(T), respectively. They work just like regular pointers, but be careful not to dereference it if it may have already been deleted! To see if it has been deleted, call ptr.was_deleted(). The only thread safe way to access its value is to call ptr.lock(), which returns nullptr if the pointer has been deleted (or is about to be), and otherwise returns a regular reference-counted PointerTo that ensures you can access it for as long as you hold it. This is a common idiom to access a weak pointer:

if (auto ptr = weak_ptr.lock()) {
  // Safely use ptr in here.
} else {
  // The pointer has been deleted.
}

Circular References

When designing your class hierarchy, you should be particularly wary of circular references. This happens when object A stores a reference to object B, but object B also stores a reference to object A. Since each object will always retain a reference to the other object, the reference count will never reach zero and memory leaks may ensue.

One way to solve this problem is to store a regular, non-reference counted pointer to object A in object B, and let object A unset the reference to itself in its destructor. This is not a general solution, however, and the most optimal solution depends on the specific situation.

Stack Allocation

In some rare cases, it is desirable to create a temporary instance of the object on the stack. To achieve this, it is necessary to call local_object() on the object directly after allocation:

Texture tex;
tex.local_object();

However, this should only be used for very temporary objects, since reference counted objects are not meant to be passed by value. Other code may assume it is safe to store a reference to it, causing the application to crash after the object goes out of scope.