Skip to content

C++

We recommend adhering to the Google C++ Style Guide for new C++ customisation artifacts. Coding style is an emotional topic, and it is hard to argue why one style is objectively better than another. If you prefer a different style, feel free to establish it in your Teamcenter project. What matters more than the specific style is consistency: once you decide on one, every developer must stick to it.

We recommend following the Google C++ Style Guide for symbol naming:

Symbol Convention Example
Class names PascalCase BomExporter
Member and free functions PascalCase AskValue
Variables snake_case object_name
Public member variables snake_case item_id
Private member variables snake_case_ (trailing underscore) cache_
Constants and enum members k + PascalCase kMyConstantValue
extern “C” declared function snake_case my_lib_free

Do not use Hungarian notation. With modern IDEs and IntelliSense, baking type information into variable names is bloat that reduces readability. It also introduces an unstandardised prefix system that drifts into inconsistency quickly.

Teamcenter ITK uses the NX memory manager. Memory returned by ITK APIs must therefore be released using the corresponding Teamcenter memory-management mechanism.

Use Teamcenter::scoped_smptr only for memory allocated and returned by Teamcenter ITK APIs.

// Avoid: manually managed ITK string
char* my_c_string = nullptr;
ResultStatus status = AOM_ask_value_string(some_object, "object_name", &my_c_string);
MEM_free(my_c_string);
// Preferred: automatically releases ITK-managed memory
Teamcenter::scoped_smptr<char> my_string{nullptr};
ResultStatus status = AOM_ask_value_string(some_object, "object_name", &my_string);

The cleanup mechanism is determined by the allocator, not by the type of data being stored. Memory containing Teamcenter types, such as tag_t, must still be released through the NX memory manager when the memory itself was allocated by an ITK API.

Teamcenter::scoped_smptr<tag_t> related_objects{nullptr};
int count = 0;
ResultStatus status = GRM_list_secondary_objects_only(primary_object, relation_type, &count, &related_objects);

Do not use Teamcenter::scoped_smptr for memory allocated by third-party libraries or by code that owns and allocates its own application-specific types. Third-party libraries may define dedicated allocators, destructors, release functions, or ownership abstractions; resources obtained from such a library must be released using the cleanup mechanism documented by that library.

For application-specific types allocated by the customisation itself, prefer standard C++ ownership abstractions:

auto value = std::make_unique<MyType>();
auto values = std::make_shared<std::vector<MyType>>();

When using low-level allocation directly, the matching deallocation mechanism must be preserved:

void* memory = std::malloc(size);
std::free(memory);
auto* object = new MyType;
delete object;
auto* objects = new MyType[count];
delete[] objects;

The governing rule is:

The allocating unit defines the ownership and cleanup mechanism.

The origin of the allocation determines how the resource must be released. Mixing allocation and deallocation mechanisms — such as malloc with delete, new with MEM_free, or an ITK allocation with std::free — results in undefined behaviour.

Avoid macros for error handling. Macros can obscure control flow and make error-handling behaviour harder to inspect. Macros are a code-smell and anti-pattern in modern C++.

// Avoid
IFERR(AOM_ask_value_tag(my_tag, "my_prop", &tag_value));

Prefer checking the return code when a failure is locally recoverable. When the failure does not invalidate the current context and can be handled meaningfully at the point where it occurs.

// Before C++17
const auto res = AOM_ask_value_tag(my_tag, "my_prop", &tag_value);
if (res != ITK_ok) {
// Handle the error here.
}
// C++17 and later
if (const auto res = AOM_ask_value_tag(my_tag, "my_prop", &tag_value); res != ITK_ok) {
// Handle the error here.
}

Prefer exception propagation when a failure invalidates the current operation or context and local recovery is not possible and cleanup, reporting, or propagation is required.

#include <base_utils/IFail.hxx>
#include <base_utils/TcResultStatus.hxx>
try {
ResultStatus status = AOM_ask_value_tag(my_tag, "my_prop", &tag_value);
} catch (const IFail& ex) {
// Clean up, report, translate, or propagate the error.
}

At its core, the ITK API and much of Teamcenter are implemented in C. Modern customisations, however, are typically written in C++, which is the recommended language for new development. Using C++ lets customisations take advantage of modern language features and the Teamcenter C++ interfaces provided through include_cpp. But integrating C++ into a predominantly C-based codebase introduces important considerations.

Exception handling is particularly critical. Allowing a C++ exception to propagate across a C ABI boundary results in undefined behaviour.

An action handler is a common example. Although the handler is declared with extern "C", its implementation may call code that throws IFail, std::exception, or another C++ exception type. For this reason, the complete handler implementation must be enclosed in a try/catch structure. It should handle known exception types explicitly and include a final catch-all block:

catch (...) {
// Prevent any exception from crossing the C ABI boundary.
}

No exception should be allowed to escape from an extern "C" entry point. This applies equally to runtime properties, rule handlers, operation extensions, and any library-exposed function that uses a C ABI. Every such entry point must prevent C++ exceptions from propagating beyond the ABI boundary.

Avoid using TC_write_syslog for application logging; it bypasses Teamcenter’s structured logging infrastructure.

Use Teamcenter::Logging::Logger instead. Logging levels are configured through TC_DATA/logger.properties and can be adjusted dynamically through the Teamcenter Management Console.

#include <mld/logging/Logger.hxx>
const auto logger =
Teamcenter::Logging::Logger::getLogger("MyCompany.MyModule");
if (logger->isDebugEnabled()) {
logger->debug("My debug message");
}

Use stable, hierarchical logger names that identify the owning module, or component. Avoid names derived from runtime data, object identifiers, or user input.

Select the appropriate logging level:

  • trace — detailed runtime diagnostics.
  • debug — diagnostic details useful during development or troubleshooting.
  • info — significant lifecycle events and normal operational milestones.
  • warn — unexpected conditions from which execution can continue.
  • error — failed operations requiring investigation.

Guard debug and trace logging when constructing the message is expensive. This avoids unnecessary string formatting, object conversion, or ITK calls when the corresponding log level is disabled.

Do not log passwords, access tokens, session credentials, personally identifiable information, or complete business-object contents. Tags and identifiers should only be logged when necessary for diagnosis.

When logging an exception, include enough context to identify the failed operation. Logging does not replace error handling: the failure must still be recovered from, translated, or propagated according to the applicable error-handling policy. Avoid logging the same failure repeatedly at multiple layers. Log it at the layer that has enough context to describe it meaningfully, or at the boundary where it is converted into a return code.