USING contemporary C++ methods WITH ARDUINO

USING contemporary C++ methods WITH ARDUINO

February 15, 2023 Digital Electronics 0

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. Per esempio:

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) { … }

privato:
    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.

privato:
  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 variabile.

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 entrancesnell’ambito circostante:

1.
2.
3.
4.
5.
6.
7.
enum color {
bianco,
blu,
giallo
};
// non si compila. C’è già qualcosa in questa gamma con il nome ‘bianco’
Auto Bianco = 5;

La variabile “BIANCO” non può essere definita poiché “BIANCO” è parte di un enum, così come tale che enum perde gli ingressi nell’ambito circostante. C ++ 11 introduce enumum oscillati che consentono un paio di cose che C ++ 98 non ha permesso. Il primo, come suggerisce il nome, è che gli organismi sono completamente scambiati. Il metodo per produrre un enum oscillato è con l’utilizzo della parola chiave ‘class’:

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
colore enum class {
bianco,
blu,
giallo
};

Auto Bianco = 5; // Questo ora funziona.

Colore c = bianco; // Errore, nulla in questo intervallo è stato definito chiamato bianco.
Colore C = Colore :: Bianco; // Corretta.

Per impostazione predefinita, gli organismi ambiti hanno un tipo sottostante: int, quindi qualunque dimensione un int è sulla tua piattaforma, questa è la dimensione che si ritorna di nuovo tornerà per il tuo enum. Prima di c ++ 11, gli organismi non schierati avevano anche un tipo sottostante, tuttavia il compilatore ha cercato di essere saggia, quindi identificherebbe ciò che il tipo sottostante era così come non ci dice – potrebbe migliorarlo per dimensioni e prodotti Un tipo sottostante che è stato il più piccolo che potrebbe in forma il numero di voci. Oppure potrebbe migliorare la velocità e produrre un tipo che era il più veloce per la piattaforma. Tutto ciò implica è che il compilatore ha capito su quale tipo un enum è stato, tuttavia non l’abbiamo, quindi non potevamo avanti inviare l’enum in un file diverso. Per esempio,

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

enum color {
bianco,
blu,
giallo
};

file2.h:

colore enum; // Errore, in questo file, il compilatore non capisce cosa sia il tipo di colore.

somefunzione void (colore c);

L’unico metodo è quello di #includere l’header1.h in header2.h. Per i piccoli progetti, va bene, tuttavia in progetti più grandi, aggiungendo un’ingresso al colore enum implicherà una ricompilazione di tutto ciò che include l’intestazione1.h o l’intestazione2.h. Gli enogiani scoperti hanno una dimensione predefinita: int, quindi il compilatore comprende sempre quali dimensioni sono. E, puoi modificare le dimensioni se lo desideri:

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

Enum Class Color: std :: int8_t {
bianco,
blu,
giallo
};

file2.h:

Colore della classe Enum: STD :: INT8_T;

somefunzione void (colore c);

Ora qualsiasi tipo di dati che include File2.h non deve necessariamente essere ricompilato (file2.cpp deve dovrà, dal momento che dovrai #include file1.h in esso in acquisto ottenere per compilare correttamente.)

In conclusione

Hai un’opzione quando si sposta continua a programmare il microcontrollore. Non c’è motivo di utilizzare qualsiasi tipo di di questi se non vuoi. Ho trovato, tuttavia, che aiutano a ripulire il mio codice oltre a aiutarmi a catturare bug a tempo di compilazione piuttosto che in tempo di esecuzione. Numerosi sono solo zucchero sintattico, così come non ti piacciono il cambiamento. Alcuni sono solo le vicetà che rendono la lavorazione in una lingua impegnativa un po ‘più bella. Cosa ti tiene indietro dal provarli?

Leave a Reply

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