Create A P2P Distributed Application In Under 35 Lines Of C++11 Code! 7

If I had to give you one reason why C++ is king (and this isn’t changing anytime soon) it would be this: C++ lets you create very powerful abstractions with zero or minimal runtime impact. Few languages can offer both.When designing your API, it’s critical that you take advantage of what the language has to offer – leveraging its strengths to enhance, among other things, its usability.

C++ is much more than C with classes, but we found that our existing RTI Connext DDS C++ API lacked this spirit. We didn’t take full advantage of its strengths, partly because we wanted to support old compilers. As a result, the API didn’t meet the needs of some of our users. Fortunately that’s going to change (very) soon!

I’m really excited to say that our new C++ API is receiving the attention it deserves. The new API, based on the OMG DDS C++ PSM includes what customers like you want to see in a modern C++ API. Designed with generic programming at its heart, the new C++ API is easy to use, STL-friendly, and ready for C++11.

To prove it, I’m going to show you how you can write your peer-to-peer distributed Hello World applications (publisher and subscriber) in under 35 lines of code, because fewer lines that do more mean less code to maintain and fewer chances to write bugs.

void publisher()
{
    DomainParticipant participant (0);
    Topic<StringTopicType> topic (participant, "Hello World Topic");
    DataWriter<StringTopicType> writer(Publisher(participant), topic);

    writer.write("Hello, World!");
}

void subscriber()
{
    DomainParticipant participant (0);
    Topic<StringTopicType> topic (participant, "Hello World Topic");
    DataReader<StringTopicType> reader (Subscriber(participant), topic);

    ReadCondition read_condition (
        reader,
        DataState::any_data(),
        [reader]()
        {
            LoanedSamples<StringTopicType> samples = reader.take();
            std::copy(
                rti::sub::valid_samples(samples.begin()),
                rti::sub::valid_samples(samples.end()),
                std::ostream_iterator<const StringTopicType&>(std::cout, "\n"));
        }
    );

    WaitSet waitset;
    waitset += read_condition;

    while (1) {
        waitset.dispatch(Duration(20));
    }
}

If you’re familiar with DDS, you probably know exactly what is going on. If you don’t, you just need to know briefly what these entities mean in the world of DDS:

  • A participant is the entity that lets you join a domain, which is a logical partition of the world identified by an ID.
  • A topic names what you are interested in (subscription) or what you know about (publication). A topic has a type associated to it.
  • A DataReader lets you receive data from a topic and a DataWriter lets you send data about a topic.

RTI Connext DDS takes care of matching subscribers and publishers of a topic and delivering data to the interested parties efficiently. Using WaitSets and Conditions is one of the patterns you can use to get notified about new data.

Basic DDS entities

DDS entities in our Hello World example

You can compare this code with a similar example in the current Connext DDS C++ API: publisher and subscriber.

You might first notice that we don’t need a single line of code for error checking or cleanup. The API throws exceptions in case of errors. You only need to handle exceptions if you have something useful to do—otherwise just let them propagate. Smart pointers manage entities such as the DomainParticipant or the DataReader and destructors take care of the cleanup for you. Even the LoanedSamples object, encapsulating data owned by the inner layers of the middleware, will return the loan in its destructor.

As I said before, generic programming dominates the API. In fact, we could have written this example for a generic data type T and barely changed anything else. If T is not a valid DDS type, you’ll find out at compile time, because the types you can use define the compile-time constant dds::topic::is_topic_type<T>::value to 1. No surprises at run-time!

The use of iterators lets us write pretty cool, succinct code, such as the call to std::copy—free of loops or conditions.

std::copy(
    rti::sub::valid_samples(samples.begin()),
    rti::sub::valid_samples(samples.end()),
    std::ostream_iterator<const StringTopicType&>(std::cout, "\n"));

That code is equivalent to:

for (rti::sub::SampleRef<StringTopicType> sample : samples) {
    if (sample.info().valid()) {
        std::cout << sample.data() << "\n";
    }
}

First, iterators let us feed our data into any generic algorithm. By using an iterator adapter (valid_samples) we iterate only through samples that contain valid data. Finally, since we tell the compiler how to automatically get the data (T) from a SampleRef<T>, you don’t need to do it yourself.

Another new and interesting pattern in the API is the association of WaitSet Conditions and their handlers. The operation WaitSet::dispatch() directly calls the handlers of the active conditions—your application no longer needs to make that association as it would in the current API.

With compiler support, you can write your function as a lambda right where it matters.

As you can see, a lot has changed to make your code shorter, easier to maintain and use, and more robust. The best part is that none of these abstractions make the application run any slower.

This was just a small sample of the new C++ API. Please leave your comments and questions here or in the RTI Community. If you want to access to a preview version, contact us at cpp_psm@rti.com. UPDATE: The Modern C++ API is already available with RTI Connext 5.2

7 comments

  1. I just heard about this from Tom Sobczynski today, and I cannot begin to tell you how exciting this is! The original C++ PSM used for DDS really was just “C with classes”, even namespaces were a bastard stepchild. While I really do understand the reasons for this, such as the glacially slow adoption of new features and compiler versions by OS vendors in the ’90s and ’00s, the world is ready for this now.

    My one negative impression: the use of operator+= with WaitSet looks unpleasant. I think most of the C++ world has learned not to be “cute” like this at least a decade ago; as Scott Meyers nicely put it, operators should “do as the ints do.” But overall it’s a vast improvement and I’m glad to see it.

    Like

    • Glad that you like it!

      Regarding the operator+= for attaching conditions to a WaitSet, I see your point. The API also provides WaitSet::attach() with the same behavior.

      Like

  2. Pingback: The Attack of the DDS C++ APIs « RTI Blog

  3. Pingback: A Noteworthy and Newsworthy 2014 « RTI Blog

  4. Pingback: Modern C++ is here! « RTI Blog

  5. Pingback: Data-Centric Stream Processing in the Fog « RTI Blog

  6. Pingback: 5 Great Dev Resources You Need to Bookmark Right Now! « RTI Blog

Submit a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s