Omni String
omni::string
is an ABI-safe, DLL-Boundary-safe string class intended to be used in Omniverse Native Interfaces that provides all of the
functionality of std::string
. The layout of omni::string
satisfies std::is_standard_layout
and will be the same
across all compilers. Using omni::string
requires carb.dll/.so
because of the
Memory functions used, which are provided by the Carbonite Framework.
Why omni::string?
Strings are frequently used in Carbonite and Omniverse interfaces, however there is not an ABI safe string
implementation available for interfaces to use. This leads to char*
and size
parameters being used, which are both
error prone and cumbersome to use. For example:
// This creates a URL from the pieces provided.
// "bufferSize" is an in-out parameter; set it to the size of the buffer before calling this function
// and it will be set to the actual size when the function returns. If the size required is more
// than the size provided, this function returns null, otherwise it returns 'buffer'.
char* omniClientMakeUrl(struct OmniClientUrl const* url, char* buffer, size_t* bufferSize);
This interface requires a confusing combination of char*
and size_t*
parameters. It requires users to preallocate a
character array which may or may not be large enough, and then may returns a nullptr
, which could lead to program
crashes if users don’t check the return value. With omni::string
, this interface could be simplified to:
// This creates a URL from the pieces provided.
omni::string omniClientMakeUrl(struct OmniClientUrl const* url);
With omni::string
, there’s no more dealing with buffer sizes or returning nullptr
which may or may not actually
get checked.
Why not std::string?
Why go through the trouble of implementing and maintaining our own string class instead of using std::string
?
There are two major reasons that std::string
is not suitable for use in Omniverse interfaces. First, std::string
is
not ABI safe. The ABI of std::string
was broken in C++11, and is not guaranteed to not be broken again in the future.
Second, std::string
is not safe to pass across DLL boundaries due to memory allocation. A std::string
that is
created with allocated storage in one module should not be passed to another module which may free the memory.
For these reasons, omni::string
was created to be the ABI-safe, DLL-boundary-safe string class in Omniverse.
Details
Small String Optimization
omni::string
provides small string optimization (SSO), so that an allocation is not required for small string. In
omni::string
, strings up to 31 characters long will be stored in the string’s internal buffer rather than in
allocated storage. Note that omni::string
is still only 32 bytes in size, so user’s do not have to pay a penalty in
stack space to get this optimization. Profiling typical workflows indicates that close to 50% of strings used in Kit
will be able to take advantage of being small string optimized.
No Templates
Unlike the Standard Library which provides a templated std::basic_string<CharT, Traits, Allocator>
class and then
typedefs std::string
, omni::string
has no template arguments. This was done primarily for ABI safety and complexity
concerns. Omniverse only uses UTF-8 characters, so the CharT
template option was removed because char
is the only
type that should be used with omni::string
. Similarly then, there was no need for a Traits
type. Finally, to
maintain DLL boundary safety, omni::string
uses the allocation methods provided by Memory,
so the Allocator
template parameter was also omitted.
A template parameter for changing the size of the SSO buffer was also originally considered, however it was ultimately rejected. This parameter would allow for the ABI of an interface to be broken simply by changing that template parameter, which could lead to difficult to diagnose issues. This parameter also led to more complex implementation, and it was decided that the benefits did not outweigh the costs.
std::string-compatible Interface
omni::string
provides an interface as close to that of std::string
as possible. All member functions and non-member
functions provided by std::string
up to C++20 are available for omni::string
. This should make it easy and familiar
to use. There are a few subtle differences however.
First, in C++17, an overload was added to most functions that took a generic T
parameter and implicitly converted it
to a std::string_view
, and performed the operation with that std::string_view
. omni::string
does not currently
implement these overloads because Carbonite headers must be compatible with C++14, which does not have the
std::string_view
type. These functions can be added in the future.
Second, most functions in std::string
became constexpr
in C++20. omni::string
makes an effort to make as many
functions as possible constexpr
, but it cannot match std::string
in this regard. std::string
takes advantage of
C++20 features that allow transient allocations via operator new
to be constexpr
, which allows any std::string
method that may grow the string, and thus trigger a reallocation, to be constexpr
. Because omni::string
uses the
Memory management functions, which are not constexpr
, it is unable to make functions
that may grow the string constexpr
.
Python Bindings
omni::string
is only intended to be used in C++, and therefore will not have Python bindings generated for the class.
Python users should continue to use Python’s native strings. Omni.bind will be extended to automatically convert Python
strings to omni::string
for interfaces that use omni::string
and have Python bindings. This will allow Python users
to seamlessly interact with interfaces using omni::string
without requiring bindings for the omni::string
class
itself.