3.2 Optional Members

In a structure type, an optional member is a member that an application can decide to send or not as part of every published sample.

A subscribing application can determine if the publishing application sent an optional member or not. Note that this is different from getting a default value for a non-optional member that did not exist in the published type (see example in Chapter 2 Type Safety and System Evolution), optional members can be explicitly unset.

Using optional members in your types can be useful if you want to reduce bandwidth usage—Connext will not send unset optional members on the wire. They are especially useful for designing large sparse types where only a small subset of the data is updated on every write.

This section explains how to define optional members in your types in IDL, XML and XSD and how to use them in applications written in C, C++, Java and in applications that use the DynamicData API. It also describes how optional members affect SQL content filters.

3.2.1 Defining Optional Members

The @optional annotation allows you to declare a struct member as optional (see Table 3.2 Declaring Optional Members). If you do not apply this annotation, members are considered non-optional.

In XSD, to declare a member optional, set the minOccurs attribute to “0” instead of “1”.

Key members cannot be optional.

Table 3.2 Declaring Optional Members

IDL

struct MyType {
    @optional int32 optional_member;
    int32 non_optional_member;
};

XML

<struct name="MyType">
    <member name="optional_member" optional="true" type="int32"/>
    <member name="non_optional_member" type="int32"/>
</struct>

XSD

<xsd:complexType name="MyType">
    <xsd:sequence>
        <xsd:element name="optional_member" minOccurs="0" 
         maxOccurs="1" type="xsd:int"/>
        <xsd:element name="non_optional_member" minOccurs="1" 
         maxOccurs="1" type="xsd:int"/>
    </xsd:sequence>
</xsd:complexType>
<!-- @struct true -->

3.2.2 Using Optional Members in an Application

This section describes how to use optional members in code generated for C/C++ and Java and with DynamicData API and SQL filters.

3.2.2.1 Using Optional Members in C and the Traditional C++ API

An optional member of type T in a DDS type maps to a pointer-to-T member in a C and C++ struct. Both optional and non-optional strings map to char *.

For example, consider the following IDL type:

struct Foo {
    string text;
};
 
struct MyType {
    @optional int32 optional_member1;
    @optional Foo optional_member2;
    int32 non_optional_member;
};

This type maps to this C or C++ structure:

typedef struct Foo {
    DDS_Char *text;
} Foo ;
 
typedef struct MyType {
    DDS_Long *optional_member1;
    Foo *optional_member2;
    DDS_Long non_optional_member;
} MyType;

An optional member is set when it points to a valid value and is unset when it is NULL. By default, when you create a data sample all optional members are NULL. The TypeSupport API includes the following operations that allow changing that behavior:

C

MyType *MyTypeTypeSupport_create_data_w_params(
        const struct DDS_TypeAllocationParams_t *alloc_params)
DDS_ReturnCode_t MyTypeTypeSupport_delete_data_w_params(
        struct Foo *a_data,
        const struct DDS_TypeDeallocationParams_t *dealloc_params);

C++

MyType *MyTypeTypeSupport::create_data(
        const DDS_TypeAllocationParams_t& alloc_params);
DDS_ReturnCode_t FooTypeSupport::delete_data(
        MyType *a_data,
        const DDS_TypeDeallocationParams_t& dealloc_params);

Set alloc_params.allocate_optional_members to true if you want to have all optional members allocated and initialized to default values.

To allocate or release specific optional string members, use the following functions both in C and traditional C++ without the command-line option -useStdString:

  • DDS_String_alloc()
  • DDS_String_free()

For traditional C++ code generated using the command line option -useStdString use:

  • new ()
  • delete

To allocate or release other specific optional members, use the following functions:

In C :

  • DDS_Heap_malloc()
  • DDS_Heap_calloc()
  • DDS_Heap_free()

In traditonal C++:

  • new ()
  • delete

You can also make an optional member point to an existing variable as long as you set it to NULL before deleting the sample.

The following C code shows several examples of how to set and unset optional members when writing samples (note: error checking has been omitted for simplicity):

/* Create and send a sample where all optional members are set */
struct DDS_TypeAllocationParams_t allocParams = DDS_TYPE_ALLOCATION_PARAMS_DEFAULT;
allocParams.allocate_optional_members = DDS_BOOLEAN_TRUE;
MyType *sample = MyTypeTypeSupport_create_data_w_params(&allocParams);
*sample->optional_member1 = 1;
strcpy(sample->optional_member2->text, "hello");
sample->non_optional_member = 2;
MyTypeDataWriter_write(
    MyType_writer,
    instance,
    &DDS_HANDLE_NIL);
 
/* This time, don't send optional_member1 */
DDS_Heap_free(sample->optional_member1);
sample->optional_member1 = NULL;
MyTypeDataWriter_write(MyType_writer, sample, &DDS_HANDLE_NIL);
 
/* Delete the sample */
retcode = MyTypeTypeSupport_delete_data_ex(sample, DDS_BOOLEAN_TRUE);
 
/* Create and send a sample where all optional members are unset */
sample = MyTypeTypeSupport_create_data_ex(DDS_BOOLEAN_FALSE);
sample->non_optional_member = 3;
MyTypeDataWriter_write(MyType_writer, sample, &DDS_HANDLE_NIL);
 
/* Now send optional_member1 */
sample->optional_member1 = (DDS_Long *)DDS_Heap_malloc(sizeof(DDS_Long));
*sample->optional_member1 = 1;
sample->non_optional_member = 3;
MyTypeDataWriter_write(MyType_writer, sample, &DDS_HANDLE_NIL);
 
/* Delete the sample */
retcode = MyTypeTypeSupport_delete_data_ex(sample, DDS_BOOLEAN_TRUE);

And this example shows how to read samples that contain optional members in C:

/* Create a sample (no need to allocate optional members here) */
struct DDS_SampleInfo info;
MyType *sample = MyTypeTypeSupport_create_data();
 
/* Read or take as usual */
MyTypeDataReader_take_next_sample(MyType_reader, sample, &info);
if (info.valid_data)
{
    printf("optional_member 1");
    if (sample->optional_member1 != NULL)
    {
        printf(" = %d", *sample->optional_member1);
    }
    else
    {
        printf("is not set \n");
    }
    printf("non_optional_member = %d", sample->non_optional_member);
}
MyTypeTypeSupport_delete_data(sample);

The following C++ code shows several examples of how to set and unset optional members when writing samples (note: error checking has been omitted for simplicity):

// Create and send a sample where all optional members are set
MyType *sample = MyTypeTypeSupport::create_data(
    DDS_TypeAllocationParams_t().set_allocate_optional_members(
        DDS_BOOLEAN_TRUE));
*sample->optional_member1 = 1;
strcpy(sample->optional_member2->text, "hello");
sample->non_optional_member = 2;
writer->write(*sample, DDS_HANDLE_NIL);
 
// This time, don't send optional_member1
delete sample->optional_member1;
sample->optional_member1 = NULL;
writer->write(*sample, DDS_HANDLE_NIL);
 
// Delete the sample
MyTypeTypeSupport::delete_data(sample);
// Create and send a sample where all optional members are unset
sample = MyTypeTypeSupport::create_data();
sample->non_optional_member = 3;
writer->write(*sample, DDS_HANDLE_NIL);
 
// Now send optional_member1:
sample->optional_member1 = new DDS_Long();
*sample->optional_member1 = 4;
writer->write(*sample, DDS_HANDLE_NIL);
 
// Delete the sample
MyTypeTypeSupport::delete_data(sample);

And this example shows how to read samples that contain optional members in traditional C++:

// Create a sample (no need to allocate optional members here)
DDS_SampleInfo info;
sample = MyTypeTypeSupport::create_data();
 
// Read or take as usual
reader->take_next_sample(*sample, info);
if (info.valid_data)
{
    std::cout << "optional_member1 ";
    if (sample->optional_member1 != NULL)
    {
        std::cout << "= " << *sample->optional_member1 << "\n";
    }
    else
    {
        std::cout << "is not set\n";
    }
    std::cout << “non_optional_member = “
        << sample->non_optional_member << “\n”;
}
// Delete the sample
MyTypeTypeSupport::delete_data(sample);

3.2.2.2 Using Optional Members in the Modern C++ API

An optional member of type T in a DDS type maps to the value-type dds::core::optional<T> in the modern C++ API.

For example, consider the following IDL type:

struct MyType {
    @optional int32 optional_member1;
    @optional Foo optional_member2;
    int32 non_optional_member;
};

This type maps to this C++ class:

class NDDSUSERDllExport MyType {
public:
	// ...
	dds::core::optional<int32_t>& optional_member1();
	const dds::core::optional<int32_t>& optional_member1() const;
	void optional_member1(const dds::core::optional<int32_t>& value);
	dds::core::optional<Foo>& optional_member2();
	const dds::core::optional<Foo>& optional_member2() const;
	void optional_member2(const dds::core::optional<Foo>& value);
	int32_t non_optional_member() const;
	void non_optional_member(int32_t value);
	// ...
};

By default optional members are unset (dds::core::optional<T>::has_value() is false). To set an optional member, simply assign a value; to reset it use reset() or assign a default-constructed optional<T>:

MyType sample; // all optional members created unset
sample.optional_member1() = 5; // now sample.optional_member1().has_value() == true
sample.optional_member1(5); // alternative way of setting the optional member
sample.optional_member2() = Foo(/* ... */);
sample.optional_member1().reset(); // now sample.optional_member1().has_value == false
sample.optional_member1() = dds::core::optional<int32_t>(); // alternative way of resetting the optional member

To get the value by reference, use value():

int x = sample.optional_member1().value(); // if !has_value(), throws dds::core::PreconditionNotMetError.
sample.optional_member2().get().foo_member(10); 

Note that dds::core::optional manages the creation, assignment and destruction of the contained value, so unlike the traditional C++ API you don't need to reserve and release a pointer.

3.2.2.3 Using Optional Members in Java

Optional members have the same mapping to Java class members as non-optional members, except that null is a valid value for an optional member. Primitive types map to their corresponding Java wrapper classes (to allow nullifying).

Generated Java classes also include a clear() method that resets all optional members to null.

For example, consider the following IDL type:

struct MyType {
    @optional int32 optional_member1;
    @optional Foo optional_member2;
    int32 non_optional_member;
};

This type maps to this Java class:

class MyType {
    public Integer optional_member1 = null;
    public Foo optional_member2 = null;
    public int non_optional_member = 0;
    // ...
    public void clear() { /* … */ }
    // ...
}

An optional member is set when it points to an object and is unset when it is null.

The following code shows several examples on how to set and unset optional members when writing samples:

// Create and send a sample with all the optional members set
MyType data = new MyType(); // All optional members are null
data.optional_member1 = 1; // Implicitly converted to Integer
data.optional_member2 = new Foo(); // Create Foo object
data.optional_member2.text = "hello";
data.non_optional_member = 2;
writer.write(data, InstanceHandle_t.HANDLE_NIL);

// This time, don't send optional_member1
data.optional_member1 = null;
writer.write(data, InstanceHandle_t.HANDLE_NIL);

// Send a sample where all the optional members are unset
data.clear(); // Set all optional members to null
data.non_optional_member = 3;
writer.write(data, InstanceHandle_t.HANDLE_NIL);

// Now send optional_optional_member1
data.optional_member1 = 4;
writer.write(data, InstanceHandle_t.HANDLE_NIL);

And this example shows how to read samples that contain optional members:

// Create a sample
MyType data = new MyType();
SampleInfo info = new SampleInfo();

// Read or take as usual
reader.take_next_sample(data, info);
if (info.valid_data) {
   System.out.print("optional_member1 ");
   if (data.optional_member1 != null) {
       System.out.println("= " + data.optional_member1);
   } else {
       System.out.println("is unset");
   }
   System.out.println("non_optional_member = " + data.non_optional_member);
}

3.2.2.4 Using Optional Members in C#

Optional members in C# map to nullable types. For all types except primitive types, the mapping is the same, except that null is a valid value, and the property is annotated with the Omg.Types.Optional attribute.

Given the following IDL:

struct MyType {
    @optional int32 optional_member1;
    @optional Foo optional_member2;
    int32 non_optional_member;
};

The C# class MyType contains the following properties:

[Optional]
public int? optional_member1 { get; set; }
 
[Optional]
public Foo optional_member2 { get; set; }
 
public int non_optional_member { get; set; }

3.2.2.5 Using Optional Member with DynamicData

This version of Connext supports a pre-standard version of DynamicData (see Chapter 7 DynamicData API). However it does support optional members.

Any optional member can be set with the regular setter methods in the DynamicData API, such as DDS_DynamicData::set_long(). An optional member is considered unset until a value is explicitly assigned using a ‘set’ operation.

To unset a member, use DDS_DynamicData::clear_optional_member().

The C and C++ ‘get’ operations, such as DDS_DynamicData::get_long(), return DDS_RETCODE_NO_DATA when an optional member is unset; in Java, the ‘get’ methods throw a RETCODE_NO_DATA exception.

The following C++ example shows how to set and unset optional members before writing a sample. The example uses the same type (MyType) as in previous sections. This example assumes you already know how to use the DynamicData API, in particular how to create a DynamicDataTypeSupport and a DynamicData topic. More information and examples are available in the API Reference HTML documentation (select Modules, RTI Connext DDS API Reference, Topic Module, Dynamic Data).

// Note: error checking omitted for simplicity
DDS_DynamicData * data = type_support.create_data();

// Set all optional members and write a sample
data->set_long("optional_member1", 
    DDS_DYNAMIC_DATA_MEMBER_ID_UNSPECIFIED, 1);

// Bind optional_member2 and set the text field
DDS_DynamicData optionalMember2(NULL, DDS_DYNAMIC_DATA_PROPERTY_DEFAULT);
data->bind_complex_member(optionalMember2, "optional_member2",
    DDS_DYNAMIC_DATA_MEMBER_ID_UNSPECIFIED);
optionalMember2.set_string("text", 
    DDS_DYNAMIC_DATA_MEMBER_ID_UNSPECIFIED, "hello");
data->unbind_complex_member(optionalMember2);
data->set_long("non_optional_member", 
    DDS_DYNAMIC_DATA_MEMBER_ID_UNSPECIFIED, 2);
writer->write(*data, DDS_HANDLE_NIL);

// This time, don't send optional_member1
data->clear_optional_member("optional_member1", 
    DDS_DYNAMIC_DATA_MEMBER_ID_UNSPECIFIED);
writer->write(*data, DDS_HANDLE_NIL);

// Delete the sample
type_support.delete_data(data);

In this example we read samples that contain optional members:

DDS_SampleInfo info;
DDS_DynamicData * data = type_support->create_data();
reader->take_next_sample(*data, info);
if (info.valid_data) {
    DDS_Long value;
    DDS_ReturnCode_t retcode = data->get_long(value,
        "optional_member1", 
        DDS_DYNAMIC_DATA_MEMBER_ID_UNSPECIFIED);
    if (retcode == DDS_RETCODE_OK) {
        std::cout << "optional_member1 = " << value << "\n";
    } else if (retcode == DDS_RETCODE_NO_DATA){
        std::cout << "optional_member1 is not set\n";
    } else {
        std::cout << "Error getting optional_member1\n";
    }
    retcode = data->get_long(value, "non_optional_member",

        DDS_DYNAMIC_DATA_MEMBER_ID_UNSPECIFIED);
    if (retcode == DDS_RETCODE_OK) {
        std::cout << "non_optional_member = " << value << "\n";
    } else {
        std::cout << "Error getting non_optional_member\n";
    }
}
// Delete the sample
type_support->delete_data(data);

3.2.2.6 Using Optional Members in SQL Filter Expressions

SQL filter expressions used in ContentFilteredTopics and QueryConditions (see Chapter 8 ContentFilteredTopics in this document and "ReadConditions and QueryConditions" and "ContentFilteredTopics" in the RTI Connext Core Libraries User's Manual) can refer to optional members. The syntax is the same as for any other member.

For example, given the type MyType:

struct Foo {
    string text;
};
struct MyType {
    @optional int32 optional_member1;
    @optional Foo optional_member2; 
    int32 non_optional_member;
};

These are valid expressions:

"optional_member1 = 1 AND optional_member2.text = 'hello' AND non_optional_member = 2"
"optional_member1 = null AND optional_member2.text <> null"

Any comparison involving an optional member (=, <>, <, or >) evaluates to false if the member is unset.

For example, both “optional_member1 <> 1” and “optional_member1 = 1” will evaluate to false if optional_member1 is unset; however “optional_member1 = 1 OR non_optional_member = 1” will be true if non_optional_member is equal to 1 (even if optional_member1 is unset). The expression “optional_member2.text = ‘hello’” will also be false if optional_member2 is unset.

To check if an optional member is set or unset, you can compare with the null keyword. The following expressions are supported:

"optional_member1 = null" *, *"optional_member1 <> null".