Generated C++ interfaces

With the transition to use IDL for specifying interfaces in ROS 2 Dashing this article has been superseded by the Interface Definition and Language Mapping article.

This article describes the generated C++ code for ROS 2 interfaces.

Authors: Dirk Thomas

Date Written: 2015-06

Last Modified: 2019-03

Scope

This article specifies the generated C++ code for ROS interface types defined in the interface definition article.

Namespacing

All code of a ROS package should be defined in a namespace named after the package. To separate the generated code from other code within the package it is defined in a sub namespace:

  • namespace for ROS messages: <package_name>::msg.
  • namespace for ROS services: <package_name>::srv.

NOTE: Using the additional sub namespace ensures that the symbols are different and don’t overlap with the ROS 1 symbols. That allows to include both in a single compilation unit like the ros1_bridge.

Generated files

Following the C++ style guide of ROS 2 the namespace hierarchy is mapped to a folder structure. The filenames use lowercase alphanumeric characters with underscores for separating words and end with either .hpp or .cpp.

Messages

For a message a templated struct with the same name followed by an underscore is generated. The single template argument is the allocator for the data structure.

For ease of use there is a typedef with the same name as the message which uses a default allocator (e.g. std::allocator).

For each message two files are being generated:

  • <my_message_name>.hpp currently only includes <my_message_name>__struct.hpp
  • <my_message_name>__struct.hpp containing the definition of the struct

This allows to add additional files besides the one with the suffix __struct to provide additional functionality. For each additional functionality it can be decided to include it from the first header file.

TODO: specify content of <my_message_name>__traits.hpp file

Types

Mapping of primitive types

ROS type C++ type
bool bool
byte uint8_t
char char
float32 float
float64 double
int8 int8_t
uint8 uint8_t
int16 int16
uint16 uint16
int32 int32
uint32 uint32
int64 int64
uint64 uint64_t
string std::string

Mapping of arrays and bounded strings

ROS type C++ type
static array std::array<T, N>
unbounded dynamic array std::vector
bounded dynamic array custom_class<T, N>
bounded string std::string

Members

The struct has same-named public member variables for every field of the message. For each field a typedef is created which is named after the member with a leading underscore and a trailing _type.

Constants

Numeric constants are defined as enums within the struct.

All other constants are declared as static const members in the struct and their values are defined outside of the struct.

Constructors

In the following discussion, “member” refers to the class member in the C++ class while “field” refers to the field definition in the IDL file.

The default constructor initializes all members with the default value specified in the IDL file, or otherwise with the common default for the field type as defined in this article (note: char fields are considered numeric for C++). In some cases this may not be desirable, since these fields will often be immediately overwritten with user-provided values. Therefore, the constructor takes an optional directive of type rosidl_generator_cpp::MessageInitialization to control how initialization is done:

  • MessageInitialization::ALL - Initialize each member with the field’s default value specified in the IDL file, or otherwise with the common default for the field type as defined in this article (note: char fields are considered numeric for C++).
    • The safest option, and also the default (used if not passing any argument to the constructor).
  • MessageInitialization::SKIP - Don’t initialize any members; it is the user’s responsibility to ensure that all members get initialized with some value, otherwise undefined behavior may result
    • Used for maximum performance if the user is setting all of the members themselves.
  • MessageInitialization::ZERO - Zero initialize all members; all members will be value-initialized (dynamic size or upper boundary arrays will have zero elements), and default values from the message definition will be ignored
    • Used when the user doesn’t want the overhead of initializing potentially complex or large default values, but still wants to ensure that all variables are properly initialized.
  • MessageInitialization::DEFAULTS_ONLY - Initialize only members that have field default values; all other members will be left uninitialized
    • Minimal initialization which ensures that existing code has correctly initialized members when a new field with a default value is added to the IDL later.

Optionally the constructor can be invoked with an allocator.

The struct has no constructor with positional arguments for the members. The short reason for this is that if code would rely on positional arguments to construct data objects changing a message definition would break existing code in subtle ways. Since this would discourage evolution of message definitions the data structures should be populated by setting the members separately, e.g. using the setter methods.

Setters

For each field a setter method is generated to enable method chaining. They are named after the fields with a leading set__. The setter methods have a single argument to pass the value for the member variable. Each setter method returns the struct itself.

Operators

The comparison operators == and != perform the comparison on a per member basis.

Pointer types

The struct contains typedefs for the four common pointer types plain pointer, std::shared_ptr, std::unique_ptr, std::weak_ptr. For each pointer type there a non-const and a const typedef:

  • RawPtr and ConstRawPtr
  • SharedPtr and ConstSharedPtr
  • UniquePtr and ConstUniquePtr
  • WeakPtr and ConstWeakPtr

For similarity to ROS 1 the typedefs Ptr and ConstPtr still exist but are deprecated. In contrast to ROS 1 they use std::shared_ptr instead of Boost.

Services

For a service a struct with the same name followed by an underscore is generated.

The struct contains only two typedefs:

  • Request which is the type of the request part of the service
  • Response which is the type of the request part of the service

The generated code is split across multiple files the same way as message are.

Request and response messages

For the request and response parts of a service separate messages are being generated. These messages are named after the service and have either a _Request or _Response suffix. They are are still defined in the srv sub namespace.