Coding Style Guidelines#
C/C++#
This guideline is a short adaptation of the Coding Style Guidelines for Omniverse Carbonite SDK, the foundational software layer for Omniverse applications,microservices, tools, plugins, Connectors, and SDKs.
Visit the Carbonite C++14 and Beyond Recommendations section for details on the use of C++14 features.
Visit the Carbonite Higher-level Concepts section for details on the use of Carbonite for thread-safety, assertions, callbacks, exceptions, logging, and other topics.
Naming#
Prefixing and Casing#
The following tables outline the naming prefixing and casing used:
Files |
Prefixing / Casing |
---|---|
namespaces |
snake_case |
headers (.h) |
PascalCase.h |
sources (.cpp) |
PascalCase.cpp |
Construct |
Prefixing / Casing |
---|---|
class, struct, enum class and typedef |
PascalCase |
constants |
kCamelCase |
enum class values |
eCamelCase |
functions |
camelCase |
private/protected functions |
_camelCase |
public member variables |
camelCase |
private/protected member variables |
m_camelCase |
private/protected static member variables |
s_camelCase |
global - static variable at file or project scope |
g_camelCase |
local variables |
camelCase |
preprocessor macros |
MACRO_CASE |
When a name includes an abbreviation or acronym that is commonly written entirely in uppercase, you must still follow the casing rules laid out above. For instance:
void* gpuBuffer; // not GPUBuffer
struct HtmlPage; // not HTMLPage
struct UiElement; // not UIElement
Naming Guidelines#
All names must be written in US English.
Use full English names. Don’t cut the words and avoid using colloquial names.
float robotVelocity; // not robotVel, or robotVelo struct Odometry; // not Odom
The following names cannot be used according to the C++ standard:
names with a double underscore anywhere are reserved (e.g.:
__buffer
,object__status
);names that begin with an underscore followed by an uppercase letter are reserved (e.g.:
_Buffer
);names that begin with an underscore are reserved in the global namespace.
Method names must always begin with a verb (avoid confusion about what a method actually does).
Consult the antonym list when naming symmetric functions.
myVector.getLength(); myObject.applyForce(x, y, z); myObject.isDynamic(); texture.getFormat();
The terms get/set or is/set (bool) should be used where an attribute is accessed directly (there is no significant computation overhead).
employee.getName(); employee.setName("Jensen Huang"); light.isEnabled(); light.setEnabled(true);
Function names must indicate when a method does significant work (e.g.:
computeXxxx()
,readXxxx()
,writeXxxx()
).float waveHeight = wave.computeHeight(); // NOT: wave.getHeight();
Use stateful names for boolean variables (e.g.:
enabled
,m_initialized
,g_cached
) and leave questions for methods (e.g.:isXxxx()
andhasXxxx()
)bool isEnabled() const; void setEnabled(bool enabled); void doSomething() { bool initialized = m_coolSystem.isInitialized(); ... }
Avoid redundancy in naming methods (the name of the object is implicit) and functions.
line.getLength(); // NOT: line.getLineLength();
Avoid public method, arguments and member names that are likely to have been defined in the preprocessor (when in doubt, use another name or prefix it).
size_t bufferMalloc; // NOT: size_t malloc; int boundsMin, boundsMax; // NOT: int min, max; void* iface; // NOT: void* interface; (Windows.h defines `interface` as a class)
Avoid conjunctions and sentences in names as much as possible. Use
Count
at the end of a name for the number of items.size_t shaderCount; // NOT: size_t numberOfShaders; VkBool32 skipCachedData; // NOT: VkBool32 skipIfDataIsCached;
Coding Rules#
Files#
All files must end in blank line.
Header files should have the extension .h.
Source files should have the extension .cpp (.cc is typically used for UNIX only and not recommended).
Header files must include the preprocessor directive to only include a header file once.
#pragma once
Source files should include the associated header in the first line of code after the commented license banner.
Header and source files should be named with PascalCase and placed in their appropriate namespaced folder paths, which are in lowercase.
Include Statements#
Do not include
Windows.h
in header files as it is monolithic and pollutes the global environment for Windows. Instead, a much slimmer CarbWindows.h exists to declare only what is needed by Carbonite. Refer to the example to see how to include it.Local includes use the path-relative include format.
Includes of files that are not local to the code (or are pulled in via package) use the search path format. Isaac Sim source files (under
plugins/
source/
) may also use search-path format for public headers (underinclude/
).If you need to associate a comment with an include put the comment on the same line as the include statement, otherwise clang-format will not move the chunk of code. Like this:
#include <stdlib.h> // this is needed for size_t on Linux
If include order is important for some files just put
// clang-format off
and// clang-format on
around those lines.
Namespaces#
Namespaces are all lowercase.
The C++ namespace should be project and/or team based and easily associated with the project (e.g.: The Isaac Sim project namespace is
isaacsim::
and is managed by the Isaac Sim team)namespace isaacsim {
We don’t add indentation for code inside namespaces (this conserves maximum space for indentation inside code).
namespace isaacsim { namespace ros2 { struct Ros2Bridge {
We don’t add comments for documenting closing of structs or definitions, but it’s OK for namespaces because they often span many pages and there is no indentation to help:
}; // end of Ros2Bridge struct <- DON'T } // namespace ros2 <- OK } // namespace isaacsim <- OK
Internal code#
For public header files, a
details
(internal) namespace should be used to declare implementation as private and subject to change, as well as signal to external users that the functions, types, etc. in thedetails
namespace should not be called.namespace details { } // namespace details
Within a translation unit (.cpp file), use an anonymous namespace to prevent external linkage or naming conflicts within a module:
namespace { } // namespace
In general, prefer anonymous namespaces over
static
.
Classes#
Classes that should not be inherited from should be declared as
final
.Each access modifier appears no more than once in a class, in the order:
public
,protected
,private
.All
public
member variables live at the start of the class.They have no prefix.
If they are accessed in a member function that access must be prefixed with
this->
for improved readability and reduced head-scratching.
All
protected
/private
member variables live at the end of the class.They are prefixed with
m_
.They should be accessed directly in member functions. Adding
this->
to access them is unnecessary.
Constructors and destructor are first methods in a class after
public
member variables unless private scoped in which case they are firstprivate
methods.The implementations in .cpp should appears in the order which they are declared in the class.
Avoid
inline
implementations unless trivial and needed for optimization.Use the
override
specifier on all overridden virtual methods. Also, every member function should have at most one of these specifiers:virtual
,override
, orfinal
.Do not override pure-virtual method with another pure-virtual method.
Here is a typical class layout
Structs#
We make a clear distinction between structs and classes.
We do not permit any member functions on structs. Those we make classes.
If you must initialize a member of the struct then use C++14 static initializers for this, but don’t do this for basic types like a Float3 struct because default construction/initialization is not free.
No additional scoping is needed on struct variables.
Not everything needs to be a class object with logic.
Sometimes it’s better to separate the data type from the functionality and structs are a great vehicle for this.
struct Float3 { float x; float y; float z; }; // check this out (structs are awesome): Float3 pointA = {0}; Float3 pointB = {1, 0, 0};
Functions#
When declaring a function that accepts a pointer to a memory area and a counter or size for the area we should place them in a fixed order: the address first, followed by the counter. Additionally,
size_t
must be used as the type for the counter.void readData(const char* buffer, size_t bufferSize); void setNames(const char* names, size_t nameCount); void updateTag(const char* tag, size_t tagLength);
Enum Classes and Bit Flags#
We use
enum class
overenum
to support namespaced values that do not collide.The values are accessed like this:
EnumName::eSomeValue
If you have an enum class as a subclass, then it should be declared inside the class directly before the constructor and destructor.
class Camera { public: enum class Projection { ePerspective, eOrthographic }; Camera(); ~Camera();
Note that any sequential or non-sequential enumeration is acceptable - the only rule is that the type should never be able to hold the value of more than one enumeration literal at any time. An example of a type that violates this rule is a bit mask. Those should not be represented by an enum. Instead use constant integers (
constexpr
) and group them by a prefix. Also, in a .cpp file you want them to also bestatic
. Below we show an example of a bit mask and bit flags:namespace isaacsim { namespace graphics { constexpr uint32_t kColorMaskRed = 0x00000001; // static constexpr in .cpp constexpr uint32_t kColorMaskGreen = 0x00000002; constexpr uint32_t kColorMaskBlue = 0x00000004; constexpr uint32_t kColorMaskAlpha = 0x00000008; } // namespace graphics namespace input { /** * Type used as an identifier for all subscriptions */ typedef uint32_t SubscriptionId; /** * Defines possible press states */ typedef uint32_t ButtonFlags; constexpr uint32_t kButtonFlagNone = 0; constexpr uint32_t kButtonFlagTransitionUp = 1; constexpr uint32_t kButtonFlagStateUp = (1 << 1); constexpr uint32_t kButtonFlagTransitionDown = (1 << 2); constexpr uint32_t kButtonFlagStateDown = (1 << 3); } // namespace input } // namespace isaacsim
Pre-processors and Macros#
It’s recommended to place preprocessor definitions in the source files instead of makefiles/compiler/project files.
Try to reduce the use of
#define
(e.g. for constants and small macro functions), and preferconstexpr
values or functions when possible.Definitions in the public global namespace must be prefixed with the namespace in uppercase:
#define ISAACSIM_API
All
#define
s should be set to 0, 1 or some other value.All checks for macros should use
#if
and not#ifdef
or#if defined()
When adding
#if
pre-processor blocks to support multiple platforms, the block must end with an#else
clause containing theCARB_UNSUPPORTED_PLATFORM()
macro. An exception to this is when the#else
block uses entirely C++ standard code; this sometimes happens in the case of platform-specific optimizations. You may not make assumptions about what features future platforms may have, aside from what’s in the C++ standard; all platform-specific code must have the associated platform specifically stated.#if CARB_PLATFORM_WINDOWS // code #elif CARB_PLATFORM_LINUX // code #elif CARB_PLATFORM_MACOS // code #else CARB_UNSUPPORTED_PLATFORM(); #endif
#if CARB_PLATFORM_WINDOWS // Windows-specific code #else // C++ standard code #endif
Macros that do not have universal appeal (i.e. are only intended to be used within a single header file) shall be prefixed with
ISAACSIMLOCAL_
and#undef
’d at the end of the file.
Commenting and documenting#
License#
The following must be included at the start (the first thing) of every header and source file:
// Copyright (c) 2024 NVIDIA CORPORATION. All rights reserved. // // NVIDIA CORPORATION and its licensors retain all intellectual property // and proprietary rights in and to this software, related documentation // and any modifications thereto. Any use, reproduction, disclosure or // distribution of this software and related documentation without an express // license agreement from NVIDIA CORPORATION is strictly prohibited. //
Header Files#
Assume customers will read comments.
Avoid spelling and grammatical errors.
Header comments use doxygen format. We are not too sticky on doxygen formatting policy.
All public functions and variables must be documented.
The level of detail for the comment is based on the complexity for the API.
Most important is that comments are simple and have clarity on how to use the API.
@brief
can be dropped and automatic assumed on first line of code. Easier to read too.@details
is dropped and automatic assumed proceeding the brief line.@param
and@return
are followed with a space after summary brief or details.
/** * Tests whether this bounding box intersects the specified bounding box (see \ref BoundingBox class). * * You would add any specific details that may be needed here. This is * only necessary if there is complexity to the user of the function. * * @param box The bounding box to test intersection with. * @returns true if the specified bounding box intersects this bounding box, false otherwise. */ bool intersects(const BoundingBox& box) const;
Overridden functions can simply refer to the base class comments.
class Bar: public Foo { protected: /** * @see Foo::render */ void render(float elapsedTime) override;
Source Files#
Clean simple code is the best form of commenting.
Do not add comments above function definitions in .cpp if they are already in header.
Comment necessary non-obvious implementation details not the API.
Only use
//
line comments on the line above the code you plan to comment.Avoid
/* */
block comments inside implementation code (.cpp). This prevents others from easily doing their own block comments when testing, debugging, etc.Avoid explicitly referring to identifiers in comments, since that’s an easy way to make your comment outdated when an identifier is renamed.
Formatting Code#
Format is enforced by
format_code.sh/format_code.bat
scripts (viarepo_format
tool) so there is no need to memorize them. However, there are some rules that must be followed.
We use a
.clang-format
file with clang-format to keep our code auto-formatted.In some rare cases where code is manually formatted in a pleasing fashion, auto-formatting can be suspended with a comment block:
// clang-format off ... Manually formatted code // clang-format on
Blocks of Code and Indentations#
Never leave conditional code statements on same line as condition test.
if (box.isEmpty()) return; // DON'T
Use braces { } even with only one statement.
if (box.isEmpty()) // OK { return; } for (size_t i = 0; i < count; ++i) { if (distance(sphere, points[i]) > sphere.radius) { return false; } }
Line Spacing#
One line of space between function declarations in source and header.
One line after each class scope section in header.
Function call spacing:
No space before bracket or just inside brackets.
One space after each comma separating parameters.
serializer->writeFloat("range", range, kLightRange);
Conditional statement spacing:
One space after conditional keywords.
No space just inside the brackets.
One space separating commas, colons and condition comparison operators.
if (enumName.compare("isaacsim::Robot::Type") == 0) { switch (static_cast<Robot::Type>(value)) { case Robot::Type::eManipulator: return "eManipulator"; ...
Don’t align blocks of variables or trailing comments to match spacing causing unnecessary code changes when new variables are introduced:
bool m_very; // Formatting float3 m_annoying; // generates ray m_nooNoo; // spurious uint32_t m_dirtyBits; // diffs.