Concise Code Using Method Chaining in Embedded C++

I recently rewrote a family of C++ libraries that I had been using for 10 years. The main themes of the rewrite include:

Method Chaining

  • Managing object lifetime
  • Writing clear and concise code

What is Method Chaining

Method chaining in C++ is when a method returns a reference to the owning object so that another method can be called.

class Object {
public:
  Object & set_value(int a){
    m_value = a;
    return *this;
  }

  Object & set_name(const char * a){
    m_name = value;
    return *this;
  }

private:
  int m_value = 0;
  const char * m_name = nullptr;
}

int do_something(const Object & object);

Here are two examples of calling do_something() with and without method chaining.

do_something() without method chaining:

Object object;
object.set_name("object");
object.set_value(100);
do_something(object);

do_something() with method chaining:

do_something(Object().set_name("object").set_value(100));

The first thing to notice is how much more concise the method chaining case is. What is more subtle is the lifetime of the Object(). Without method chaining, object lives as an lvalue until it goes out of scope. You could add {} to expedite that process. But that would also make the code even less concise.

With method chaining, the lifetime of Object() is naturally minimized allowing any resources used by an object to be released as soon as do_something() finishes.

Using Macros

It can get quite tedious to write setters for classes that support method chaining. I get around this by creating macros for both getters and setters allowing class member variables to be declared in a single line of code.

This macro declares a bool member:

#define API_ACCESS_FUNDAMENTAL(c, t, v, iv)                                    \
public:                                                                        \
  t v() const { return m_##v; }                                                \
  c &set_##v(t value) {                                                        \
    m_##v = value;                                                             \
    return *this;                                                              \
  }                                                                            \
                                                                               \
private:                                                                       \
  t m_##v = iv

Object then becomes:

class Object {
  API_ACCESS_FUNDAMENTAL(Object,int,value,0);
  API_ACCESS_FUNDAMENTAL(Object,const char *,name,nullptr);
}

This has the bonus of forcing you to provide an initial value to all members. This has saved me debugging time on more than one occasion.

So What?

Method chaining all by itself can help manage object lifetime while enabling clear and concise code, but its true power is shown when combined with other techniques like strong arguments and filesystems like abstraction. You can check out more examples and see the full source code on Github.