USING contemporary C++ methods WITH ARDUINO


C++ has been rapidly modernizing itself over the last few years. starting with the introduction of C++11, the language has made a big step ahead as well as things have altered under the hood. To the typical Arduino user, a few of this is irrelevant, perhaps many of it, however the language still provides us some good features that we can take advantage of as we program our microcontrollers.

Modern C++ enables us to compose cleaner, much more concise code, as well as make the code we compose much more reusable. The complying with are some methods utilizing new features of C++ that don’t add memory overhead, decrease speed, or boost size since they’re all handled by the compiler. utilizing these features of the language you no longer have to concern about specifying a 16-bit variable, calling the wrong function with NULL, or peppering your constructors with initializations. The old methods are still offered as well as you can still utilize them, however at the extremely least, after reading this you’ll be much more conscious of the newer features as we begin to see them roll out in Arduino code.

How huge are your integers?

C++11 introduced a series of fixed width integer types aliases that will provide you the number of bits (in multiples of 8) you want. There are both signed as well as unsigned versions. If you utilize int16_t for a variable you understand it’s 16 bits, regardless of which Arduino is being targeted.

1
2
3
4
int8_t/uint8_t – a signed/unsigned type that is precisely 8 bits in size.
int16_t/uint16_t – a signed/unsigned type that is precisely 16 bits in size.
int32_t/uint32_t – a signed/unsigned type that is precisely 32 bits in size.
int64_t/uint64_t – a signed/unsigned type that is precisely 64 bits in size.

These are aliases of the underlying types, so on Arduino Uno, int8_t is the exact same type as a char, as well as uint16 is the exact same size as an unsigned int. One note, if you’re editing a ‘.cpp’ file, you’ll have to #include , however in a ‘.ino’ file, you don’t.

Anonymous Namespaces

The idea of an anonymous namespace has been around for a while in C++, however it concerned prominence with the introduction of C++11. In C++11, the anonymous namespace is now the favored method to specify a variable, function, or class that is accessible only in the present data (ie., they have internal, rather than external, linkage). It’s likewise a good method to consolidate things which only requirement to be accessed within the present file. anonymous namespace are likewise called ‘unnamed namespaces’ because, as the name suggests, they are defined without a name:

1
2
3
namespace {
  …
}

Anonymous namespaces take the location of declaring things static. Anything you may have declared as static or const, or written as a #define can now put into an anonymous namespace as well as have the exact same impact – anything defined in inside cannot be accessed outside of the ‘.cpp’ data that the namespace is in. In the Arduino IDE, though, if you add a tab as well as provide the new tab a name that doesn’t end in ‘.cpp’ or ‘.h’ then the data is provided an extension of ‘.ino.’ When you click the ‘Verify’ button, all the ‘.ino’ data are concatenated into a single ‘.cpp’ file. This implies that anything you state as being static or const or in an anonymous namespace will be offered in each ‘.ino’ data because, when they are concatenated, they all end up in the exact same ‘.cpp’ file. This typically isn’t a issue for little Arduino programs, however it’s great to be conscious of it.

Okay, so now we understand about interior as well as outside linkages as well as exactly how data are concatenated before being compiled. exactly how does this assist us in our code? Well, we now understand exactly how to define variables to ensure that they won’t leak into areas they’re not expected to be.

rather than write:

1
2
3
4
5
6
// This ought to be utilized sparingly anyway
#define SOME_VAR 1000    
// static variables declared in a data are regional to the file
static int16_t count = 0;    
// const variables declared in a data are regional to the data as well
const int16_t numLeds = 4;  

you can now write:

1
2
3
4
5
6
7
8
9
namespace {
  const int16_t SOME_VAR = 1000;  // now it’s type-safe
  int16_t count = 0;  // No requirement to utilize static
  const int16_t numLeds = 0;  // Still declared const.

  class thisClassHasACommonName {
    …
  };
}

Everything’s contained within the anonymous namespace at the beginning of the file. The compiler won’t get confused if there’s one more SOME_VAR, count or numLeds defined in a different file. as well as unlike static, classes declared in an anonymous namespace are regional to the data as well.

Automatic for the people

The car keyword, added in C++11, enables you to define a variable without understanding its type. when defined, though, like other variables, it’s type can’t be changed, just like routine C++ variables. The C++ compiler utilizes deduction figure out the variable’s type.

1
2
3
auto i = 5;          // i is of type int
auto j = 7.5f;       // j is of type float
auto k = GetResult();  // k is of whatever type GetResult() returns

in purchase for you to specify that you want a reference, you can do this:

1
2
auto& temp1 = myValue;
const auto& temp2 = myValue;

The appropriate type is deduced for tips as well:

1
2
int myValue = 5;
auto myValuePtr = &myValue; // myValuePtr is a tip to an int.

Auto is a fantastic shorthand for those particularly long, challenging types.  It works fantastic for defining iterator instances!

Using using

There have been a couple of methods to produce aliases in C++: The lowly (and dangerous) #define as well as the less harmful typedef. The utilize of typedef is favored to a #define, however they do have a couple of issues. The very first is readability. think about the complying with typedef:

1
typedef void(*fp)(int, const char*);

At first glance, particularly if you don’t utilize C++ a lot, it can be challenging to identify that this is producing an alias, fp, that is a tip to a function that returns space as well as takes an int as well as a string parameter.  Now, let’s see the C++11 way:

1
using fp = void(*)(int, const char*);

We can now see that fp is an alias to something, as well as it’s a bit simpler to identify that it’s a function tip that it’s an alias of.

The second difference is a bit much more esoteric, at least in Arduino programming. Usings can be templatized while typedefs cannot.

Nullptr

In C as well as C++98, NULL is really defined as 0. To be backwards compatible, the C++ compiler will enable you to initialize a tip variable using 0 or NULL.

When you begin utilizing auto, though, you’ll begin seeing code like this:

1
2
3
auto result = GetResult(…);

if (result == 0) { … }

Just from taking a look at this, you can’t tell if GetResult returns a tip or an integer. However, even among those who still utilize C, not numerous will inspect if result == 0, they’ll inspect if result == NULL.

1
2
3
auto result = GetResult(…);

if (result == NULL) { … }

If, later on, you modification GetResult to return an int, there’s no compiler error – the statement is still valid, although it still appears like it ought to be a pointer.

C++11 modifications this by introducing nullptr, which is an actual tip type. Now, you type:

1
2
3
auto result = GetResult(…);

if (result == nullptr) { … }

Now you understand that GetResult can only return a pointer. as well as if somebody modifications it on you, then they’ll get a compile error if they don’t likewise modification the if statement on you. The introduction of nullptr likewise implies that it’s safer to overload a technique on an integral type as well as a tip type. Par exemple:

1
2
3
4
5
void SetValue(int i); // (1)
void SetValue(Widget* w); // (2)

SetValue(5);    // phone calls 1
SetValue(NULL); // likewise phone calls 1

Because NULL isn’t a pointer, the second contact us to SetValue phone calls the version that takes an integer parameter. We can now phone call the second SetValue correctly by utilizing nullptr:

1
SetValue(nullptr);  // phone calls 2

This is why it’s typically thought about harmful to overload a function or technique based on an integer parameter as well as a tip (to anything). It’s safer now, however still frowned upon.

Default Initialization

Considering the complying with class:

1
2
3
4
5
6
7
8
class Foo {
  Foo() : fooString(nullptr) { … }
  Foo(const char* str) : fooString(nullptr) { … }
  Foo(const Foo& other) : fooString(nullptr) { … }

privé:
    char* fooString;
};

We’ve initialized all the variable with nullptr, which is good. If one more member variable is added to this class we now have to add three much more initializations to the constructors. If your class has a number of variables, you have to add initializers for all of them in all the constructors. C++11 provides you the choice to initialize variables inline with the declaration.

1
2
3

privé:
  char* fooString = nullptr;

With C++11, we can specify a default preliminary value – we can still override this in each constructor if we requirement to, but, if we don’t, it doesn’t matter exactly how numerous constructors we add, we only requirement to set the value in one place. If we’ve separated our class out in to a ‘.h’ data as well as a ‘.cpp’ file, then an added benefit is that we only have to open the ‘.h’ data to add as well as initialize a variable.

Scoping Your Enums

One of the things that C++ tries to do is enable the programmer to encapsulate things so that, for example, when you name a variable, you’re not unintentionally naming your variable the exact same as something else with the exact same name. C++ provides you tools to enable you to do this, such as namespaces as well as classes. The lowly enum, however, leaks its entrancesinto the surrounding scope:

1
2
3
4
5
6
7
enum Colour {
blanche,
bleu,
jaune
};
// Doesn’t compile.  There’s already something in this range with the name ‘white’
auto white = 5; 

The variable ‘white’ can’t be defined since ‘white’ is part of an enum, as well as that enum leaks it’s entrances into the surrounding scope. C++11 introduces scoped enums which enable a couple of things that C++98 didn’t allow. The first, as the name implies, is that the enums are completely scoped. The method to produce a scoped enum is with the utilize of the ‘class’ keyword:

1
2
3
4
5
6
7
8
9
dix
enum class Colour {
blanche,
bleu,
jaune
};

auto white = 5; // This now works.

Colour c = white; // Error, nothing in this range has been defined called white.
Colour c = Colour::white; // Correct.

By default, scoped enums have an underlying type: int, so whatever size an int is on your platform, that’s the size that sizeof will return for your enum. before C++11, unscoped enums likewise had an underlying type, however the compiler tried to be wise about it, so it would identify what the underlying type was as well as not tell us – it might enhance it for size as well as produce an underlying type that was the smallest that might in shape the number of entries. Or it might enhance for speed as well as produce a type that was the fastest for the platform. All this implies is that the compiler understood what type an enum was, however we didn’t, so we couldn’t ahead state the enum in a different file. Par exemple,

1
2
3
4
5
6
7
8
9
dix
11
12
13
file1.h:

enum Colour {
blanche,
bleu,
jaune
};

file2.h:

enum Colour; // Error, in this file, the compiler doesn’t understand what the type of Colour is.

void SomeFunction(Colour c);

The only method is to #include header1.h in header2.h. For little projects, this is fine, however in bigger projects, adding an entry to the Colour enum will imply a recompilation of anything that includes header1.h or header2.h. Scoped enums have a default size: int, so the compiler always understands what size they are. And, you can modification the size if you wish:

1
2
3
4
5
6
7
8
9
dix
11
12
13
file1.h:

enum class Colour: std::int8_t {
blanche,
bleu,
jaune
};

file2.h:

enum class Colour: std::int8_t;

void SomeFunction(Colour c);

Now any type of data that includes file2.h doesn’t necessarily have to be recompiled (file2.cpp will have to, since you’ll have to #include file1.h in it in purchase get it to compile properly.)

En conclusion

You do have option when moving ahead programming your microcontroller. There’s no reason to utilize any type of of these if you don’t want to. I’ve found, however, that they assist clean up my code as well as assist me catch bugs at compile time rather than at run time. numerous are just syntactic sugar, as well as you may not like the change. Some are just niceties that make working in a challenging language a bit nicer. What’s holding you back from trying them out?

Leave a Reply

Your email address will not be published. Required fields are marked *