|
Reference counting. More...
Go to the source code of this file.
Classes | |
class | TfRefPtr< T > |
Reference-counted smart pointer utility class. More... | |
Macros | |
#define | TF_SUPPORTS_REFPTR(T) std::is_base_of_v<TfRefBase, T> |
Reference counting.
Here is how to make a class Bunny
usable through TfRefPtr
.
Pretty much any pointer operation that is legal with regular pointers is legal with the BunnyRefPtr
; continue reading for a more detailed description.
Note that by virtue of deriving from TfRefBase
, the reference count can be queried: see TfRefBase
for details.
Objects created by the new
operator can easily be a source of both memory leaks and dangling pointers. One solution is reference counting; when an object is created by new
, the number of pointers given the object's address is continually tracked in a reference counter. Then, delete
is called on the object only when the object's reference count drops to zero, meaning there are no more pointers left pointing to the object. Reference counting avoids both dangling pointers and memory leaks.
Access by regular pointers does not perform reference counting, but the TfRefPtr<T>
class implements reference-counted pointer access to objects of type T
, with minimal overhead. The reference counting is made thread-safe by use of atomic integers.
Basic Use
The use of a TfRefPtr
is simple. Whenever a TfRefPtr
is made to point at an object, either by initialization or assignment, the object being pointed at has its reference count incremented. When a TfRefPtr
with a non-NULL address is reassigned, or goes out of scope, the object being pointed to has its reference count decremented.
A TfRefPtr<T>
can access T's
public members by the ->
operator and can be dereferenced by the "\c *" operator. Here is a simple example:
Note that Simple's
constructor is private; this ensures that one can only get at a Simple
through Simple::New()
, which is careful to return a SimpleRefPtr
.
Note that it is often useful to have both const and non-const versions of TfRefPtr
for the same data type. Our convention is to use a typedef
for each of these, with the const version beginning with "Const", after any prefix. The const version can be used as a parameter to a function in which you want to prevent changes to the pointed-to object. For example:
It is always possible to assign a non-const pointer to a const pointer variable. In extremely rare cases where you want to do the opposite, you can use the TfConst_cast
function, as in:
Comparisons and Tests
Reference-counted pointers of like type can be compared; any TfRefPtr
can be tested to see it is NULL or not:
Opaqueness
A TfRefPtr
can be used as an opaque pointer, just as a regular pointer can. For example, without having included the header file for a class XySimple
, the following will still compile:
Note that the call Forget()
(or SetSimple()
for that matter) may implicitly cause destruction of an XySimple
object, if the count of the object pointed to by _simplePtr
drops to zero. Even though the definition of XySimple
is unknown, this compiles and works correctly.
The only time that the definition of XySimple
is required is when putting a raw XySimple*
into a TfRefPtr<XySimple>
; note however, that this should in fact only be done within the class definition of XySimple
itself.
Other cases that require a definition of XySimple
are parallel to regular raw pointers, such as calling a member function, static or dynamic casts (but not const casts) and using the TfTypeid
function. Transferring a TfWeakPtr
to a TfRefPtr
also requires knowing the definition of XySimple
.
Sometimes a class may have many typedefs associated with it, having to do with TfRefPtr
or TfWeakPtr
. If it is useful to use the class opaquely, we recommend creating a separate file as follows:
The definition for XySimple
would then use the typedefs:
The above pattern now allows a consumer of class XySimple
the option to include only simplePtrDefs.h
, if using the type opaquely suffices, or to include simple.h
, if the class definition is required.
Cyclic Dependencies
If you build a tree using TfRefPtr
, and you only have pointers from parent to child, everything is fine: if you "lose" the root of the tree, the tree will correctly destroy itself.
But what if children point back to parents? Then a simple parent/child pair is stable, because the parent and child point at each other, and even if nobody else has a pointer to the parent, the reference count of the two nodes remains at one.
The solution to this is to make one of the links (typically from child back to parent) use a TfWeakPtr
. If a class T
is enabled for both "guarding" (see TfWeakBase
) and reference counting, then a TfRefPtr
can be converted to a TfWeakPtr
(in this context, a "back pointer") and vice versa.
Inheritance
Reference-counted pointers obey the same rules with respect to inheritance as regular pointers. In particular, if class Derived
is derived from class Base
, then the following are legal:
As the last example shows, initialization or assignment to a TfRefPtr<Derived>
from a TfRefPtr<Base>
is illegal, just as it is illegal to assign a Base*
to a Derived*
. However, if Derived
and Base
are polymorphic (i.e. have virtual functions) then the analogue of a dynamic_cast<>
is possible:
Just like a regular dynamic_cast<>
operation, the TfRefPtr
returned by TfDynamic_cast<>
points to NULL if the conversion fails, so that the operator can also be used to check types:
Similarly, one can use the TfStatic_cast<>
operator to statically convert TfRefPtrs:
This is faster, but not as safe as using TfDynamic_cast
.
Finally, remember that in C++
, a Derived**
is not a Base**
, nor is a Derived*&
a Base*&
. This implies that
The last initialization is legal because the compiler implicitly converts dPtr into a temporary variable of type TfRefPtr<Base>
.
Thread Safety
One more comment about thread-safety: the above examples are thread-safe in the sense that if two or more threads create and destroy their own TfRefPtr
objects, the reference counts of the underlying objects are always correct; said another way, the reference count it a thread-safe quantity.
However, it is never safe for two threads to simultaneously try to alter the same TfRefPtr
object, nor can two threads safely call methods on the same underlying object unless that object itself guarantees thread safety.
The TfRefPtrTracker
singleton can track TfRefPtr
objects that point to particular instances. The macros TF_DECLARE_REFBASE_TRACK
and TF_DEFINE_REFBASE_TRACK
are used to enable tracking. Tracking is enabled at compile time but which instances to track is chosen at runtime.
Total Encapsulation If you're using TfRefPtrs
on a type T
, you probably want to completely forbid clients from creating their own objects of type T
, and force them to go through TfRefPtrs
. Such encapsulation is strongly encouraged. Here is the recommended technique:
Clients can now only create objects of type Simple
using a TfRefPtr:
Definition in file refPtr.h.