<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=135637837074656&amp;ev=PageView&amp;noscript=1">

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

July 31, 2014 by
|

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.

dds-intro-cpp11-blog.pngDDS 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

Let Us Know What You Thought of the Article.

Leave a Comment Below.

30-DAY FREE TRIAL

RTI_Blog_Latest-Resource-Module_30-Day-Eval_V0_500x500_0817.jpg
Get Started

Subscribe to Email Updates