All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
refPtr.h File Reference

Reference counting. More...

+ Include dependency graph for refPtr.h:
+ This graph shows which files directly or indirectly include this file:

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>
 

Detailed Description

Reference counting.

Quick Start

Here is how to make a class Bunny usable through TfRefPtr.

typedef TfRefPtr<Bunny> BunnyRefPtr;
class Bunny : public TfRefBase {
public:
static BunnyRefPtr New() {
// warning: return new Bunny directly will leak memory!
return TfCreateRefPtr(new Bunny);
}
static BunnyRefPtr New(bool isRabid) {
return TfCreateRefPtr(new Bunny(isRabid));
}
~Bunny();
bool IsHungry();
private:
Bunny();
Bunny(bool);
};
BunnyRefPtr nice = Bunny::New();
BunnyRefPtr mean = Bunny::New(true); // this one is rabid
BunnyRefPtr mean2 = mean; // two references to mean rabbit
mean.Reset(); // one reference to mean rabbit
if (mean2->IsHungry())
nice.Reset(); // nice bunny gone now...
// this function comes from
// TfRefBase; meaning is that
if (mean2->IsUnique()) // mean2 is the last pointer to
mean2.Reset(); // this bunny...
Enable a concrete base class for use with TfRefPtr.
Definition: refBase.h:56
Reference-counted smart pointer utility class.
Definition: refPtr.h:590
Reference counting.

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.

Detailed Discussion: Overview

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:

typedef TfRefPtr<Simple> SimpleRefPtr;
class Simple : public TfRefBase {
public:
void Method1();
int Method2();
static SimpleRefPtr New() {
return TfCreateRefPtr(new Simple);
}
private:
Simple();
};
SimpleRefPtr p1; // p1 points to NULL
SimpleRefPtr p2 = Simple::New(); // p2 points to new object A
SimpleRefPtr p3 = Simple::New(); // p3 points to new object B
p1->Method1(); // runtime error -- p1 is NULL
p3 = p2; // object B is deleted
p2->Method1(); // Method1 on object A
int value = p3->Method2(); // Method2 on object A
p2.Reset(); // only p3 still points at A
p3 = p1; // object A is deleted
if (...) {
SimpleRefPtr p4 = Simple::New(); // p4 points to object C
p4->Method1();
} // object C destroyed

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:

typedef TfRefPtr<XySimple> XySimpleRefPtr;
typedef TfRefPtr<const XySimple> XyConstSimpleRefPtr;
void
Func1(const XySimpleRefPtr &p)
{
p->Modify(); // OK even if Modify() is not a const member
}
void
Func2(const XyConstSimpleRefPtr &p)
{
p->Query(); // OK only if Query() is a const member
}

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:

XyConstSimpleRefPtr p1;
XySimpleRefPtr p2;
p1 = p2; // OK
p2 = p1; // Illegal!
p2 = TfConst_cast<XySimpleRefPtr>(p1); // OK, but discouraged

Comparisons and Tests

Reference-counted pointers of like type can be compared; any TfRefPtr can be tested to see it is NULL or not:

TfRefPtr<Elf> elf = Elf::New();
sneezy = Dwarf::New();
if (!sleepy)
... // true: sleepy is NULL
if (sneezy)
... // true: sneezy is non-nULL
bool b1 = (sleepy != sneezy),
b2 = (sleepy == sneezy),
b3 = (elf == sneezy); // compilation error -- type clash

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:

class XySimple;
class Complicated {
public:
void SetSimple(const TfRefPtr<XySimple>& s) {
_simplePtr = s;
}
TfRefPtr<XySimple> GetSimple() {
return _simplePtr;
}
void Forget() {
_simplePtr.Reset();
}
private:
TfRefPtr<XySimple> _simplePtr;
};
void Reset()
Set this pointer to point to no object.
Definition: refPtr.h:967

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:

// file: proj/xy/simplePtrDefs.h
typedef TfRefPtr<class XySimple> XySimpleRefPtr;
typedef TfRefPtr<const class XySimple> XyConstSimpleRefPtr;
// typedefs for TfWeakPtr<XySimple> would follow,
// if XySimple derives from TfWeakBase
Pointer storage with deletion detection.

The definition for XySimple would then use the typedefs:

#include "Proj/Xy/SimpleRefPtrDefs.h"
class XySimple : public TfRefBase {
public:
static XySimpleRefPtr New();
...
};

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:

TfRefPtr<Base> bPtr = new Base;
TfRefPtr<Derived> dPtr = new Derived;
TfRefPtr<Base> b2Ptr = dPtr; // initialization
b2Ptr = dPtr; // assignment
b2Ptr == dPtr; // comparison
dPtr = bPtr; // Not legal: compilation error

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:

dPtr = TfDynamic_cast<TfRefPtr<Derived> >(bPtr);

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:

if (! TfDynamic_cast<TfRefPtr<T2> >(ptr))
// complain: ptr is not of type T2

Similarly, one can use the TfStatic_cast<> operator to statically convert TfRefPtrs:

dPtr = TfStatic_cast<TfRefPtr<Derived> >(bPtr);

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

TfRefPtr<Base>* bPtrPtr = &dPtr; // compilation error
TfRefPtr<Base>& bPtrRef = dPtr; // compilation error
const TfRefPtr<Base>&bPtrRef = dPtr; // OK

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.

Tracking References

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:

typedef TfRefPtr<class Simple> SimpleRefPtr;
class Simple : public TfRefBase {
private: // use protected if you plan to derive later
Simple();
Simple(<arg-list>);
public:
static SimpleRefPtr New() {
return TfCreateRefPtr(new Simple);
}
static SimpleRefPtr New(<arg-list>) {
return TfCreateRefPtr(new Simple(<arg-list>));
}
~Simple();
};

Clients can now only create objects of type Simple using a TfRefPtr:

Simple s; // compilation error
SimpleRefPtr sPtr1 = new Simple; // compilation error
SimpleRefPtr sPtr2 = Simple::New(); // OK
SimpleRefPtr sPtr3 = Simple::New(<arg-list>); // Ok

Definition in file refPtr.h.

Macro Definition Documentation

◆ TF_SUPPORTS_REFPTR

#define TF_SUPPORTS_REFPTR (   T)    std::is_base_of_v<TfRefBase, T>

Definition at line 1334 of file refPtr.h.