HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
String Classes

Table Of Contents

UT_StringRef and UT_StringHolder

A UT_StringRef stores a reference to an immutable string, along with the string's length. If the UT_StringRef is ever hashed, the hash value is cached (and is passed on to any copies of the string).

Since a UT_StringRef always knows its length, the UT_StringRef(const char *) constructor will invoke strlen(). Constructors are also provided to create a UT_StringRef from other common string types, such as std::string and UT_WorkBuffer, and those constructors will avoid the strlen() call.

A UT_StringRef has two ownership modes: it can store a shallow reference to a const char *, or it can have reference-counted ownership of the string. Copies of a UT_StringRef are therefore cheap and do not duplicate the referenced string.

Comparing UT_StringRef's for equality can be very efficient, since a strcmp() call can be avoided if the two UT_StringRef's reference the same string or have different lengths.

UT_StringHolder is a subclass of UT_StringRef, for which the lifetime of the referenced string is guaranteed to exceed that of the UT_StringHolder (and any copies of it). A UT_StringRef should never be copied into a container or member variable, since there are no guarantees about whether the referenced string will outlive the UT_StringRef copy.

The constructors for UT_StringRef only allow creating a shallow reference to a const char *. The constructors for UT_StringHolder allow creating either a shallow reference to the string, or a copy of the string with reference-counted ownership. When creating a UT_StringHolder that stores a shallow reference to a const char *, the caller must ensure that the referenced string will outlive the UT_StringHolder and any copies of it.

As mentioned above, it is assumed that if a UT_StringRef contains a shallow reference to a string, the referenced string may not necessarily outlive any copies of the UT_StringRef. For such UT_StringRef's, the UT_StringHolder(const UT_StringRef &) constructor will make a copy of the string. Since UT_StringHolder is a subclass of UT_StringRef, a UT_StringHolder can be used wherever a UT_StringRef is required without any overhead.

UT_StringRef is useful for string parameters where you need the length or hash, as in the example below. If a string will be stored somewhere (e.g. in a container or member variable), UT_StringHolder should be used. For situations where a string parameter is only sometimes stored, use UT_StringRef for the parameter and convert it to a UT_StringHolder when needed (demonstrated in the example code for UT_StringMap and UT_StringSet). If the string parameter will always be stored, using UT_StringHolder is preferable since it allows UT_StringHolder's with shallow references to be used.

UT_StringMap<T> mySymbolTable;
const T *find(const UT_StringRef &key)
{
auto it = mySymbolTable.find(key);
return it != mySymbolTable.end() ? it->second : nullptr;
}
// Creates a temporary UT_StringRef with a shallow reference to the string
// (implicitly calling strlen()), and then computes the hash.
find("foo");
// Creates a temporary UT_StringRef with a shallow reference to the string
// (using the cached string length from std::string), and then computes the
// hash.
std::string bar("bar");
find(bar);
// UT_StringRef caches the length and hash value, so the second call to
// find() will avoid a strlen() and string hash.
UT_StringRef bar_ref(bar);
find(bar_ref);
find(bar_ref);
// UT_StringHolder is a subclass of UT_StringRef, and so can be used
// wherever a UT_StringRef is needed.
UT_StringHolder holder("foo"); // Makes a deep copy and calls strlen().
find(holder); // Computes the hash.
UT_StringHolder holder2 = holder; // Bumps the reference count, and copies the length and hash.
find(holder2); // Hash is re-used.

UTmakeUnsafeRef() and UTmakeUnsafeRefHash()

UTmakeUnsafeRef() is a convenience function that casts a UT_StringRef to a UT_StringHolder. This function has two primary use cases:

UTmakeUnsafeRefHash() is an extension of UTmakeUnsafeRef() that also forces the string's hash to be computed. If the UT_StringHolder will be used frequently in hash tables, this may avoid some unnecessary hashing. For a UT_StringHolder that has reference counted ownership of the string, the cached hash is shared between all owners, but this is not the case for shallow references. As a result, if e.g. a shallow reference will be copied twice and each copy is hashed, it is more efficient to use UTmakeUnsafeRefHash() and force the hash to be computed on the original UT_StringHolder (since copying a UT_StringRef / UT_StringHolder copies the cached hash).

UT_StringRef in Houdini 14

The semantics of UT_StringRef changed significantly between Houdini 14 and Houdini 15. In Houdini 14, UT_StringRef was a subclass of UT_StringHolder that created a UT_StringHolder with a shallow reference to a string, which is equivalent to UTmakeUnsafeRef() in Houdini 15. Therefore, constructing a UT_StringHolder from a UT_StringRef will have different behavior in Houdini 14 and 15. UT_StringRef was not widely used in the HDK in Houdini 14, so the impact of this change on existing code should be limited. However, it is important to be cautious when using UT_StringRef in code that will be compiled for both Houdini 14 and 15.

UT_StringMap and UT_StringSet

UT_StringMap and UT_StringSet are specializations of UT_Map and UT_Set which take advantage of UT_StringRef and UT_StringHolder to provide improved performance. You should always use these instead of UT_Map<UT_StringHolder, T> or UT_Set<UT_StringHolder>. For UT_SortedMap and UT_SortedSet, UT_SortedStringMap and UT_SortedStringSet provide equivalent functionality.

For any unordered container methods that take a const UT_StringHolder & parameter and do not store the key in the container (e.g. find(), erase(), etc.), these specializations provide a const UT_StringRef & overload that casts the UT_StringRef to a UT_StringHolder via UTmakeUnsafeRef() and forwards it to the regular method. This makes it much more efficient to use other string types when querying the container, as a deep copy is avoided.

...
// Implicitly constructs a UT_StringHolder and copies the string.
slow_map.find("foo");
// Constructs a UT_StringRef with a shallow reference to the string.
fast_map.find("foo");
// Only converts the UT_StringRef to a UT_StringHolder if necessary, and
// the string is only ever hashed once.
int getId(const UT_StringRef &str)
{
auto it = fast_map.find(str);
if (it != fast_map.end())
return it->second;
else
{
int id = fast_map.size();
// Constructs a UT_StringHolder from the UT_StringRef, and copies
// the cached hash from the previous find() call.
// For the first call to getId(), the UT_StringRef is a shallow
// reference and so a deep copy is made.
// For the second call, the UT_StringRef already owns the string
// and so ownership is shared with the new UT_StringHolder.
fast_map[str] = id;
return id;
}
}
getId("foo");
UT_StringHolder holder("bar");
getId(holder);