Cpp Coding Guide

User Manual:

Open the PDF directly: View PDF PDF.
Page Count: 32

Member of the PICANOL GROUP
µC & DSP TEAM
C++ Coding Guide
Jens Jonckheere
September 30, 2018
Writing clean code is what you must do in order to call yourself a professional.
There is no reasonable excuse for doing anything less than your best.
– Robert C. Martin [5]
Contents
Foreword v
1 Coding Style 1
1.1 Formatting...................................... 1
1.1.1 ModuleFiles ................................ 1
1.1.2 ClassFiles ................................. 2
1.1.3 OrderofIncludes[1]............................ 3
1.1.4 Functions.................................. 3
1.1.5 Ifstatements ................................ 4
1.1.6 Switchstatements ............................. 4
1.1.7 Loops.................................... 5
1.1.8 ArrayInitialization............................. 5
1.1.9 Preprocessor Directives . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2 Initialization..................................... 6
1.2.1 FundamentalTypes............................. 6
1.2.2 Arrays.................................... 7
1.2.3 ClassesandStructs............................. 7
1.2.4 Classes ................................... 8
1.2.5 Templates.................................. 8
1.3 NamingConventions ................................ 8
1.3.1 Arrays.................................... 8
1.3.2 Enums ................................... 8
1.4 Miscellaneous.................................... 9
ii
2 Clean Code 10
2.1 PerformanceandSafety............................... 10
2.1.1 Comparisons ................................ 10
2.2 Variables ...................................... 11
2.3 Functions ...................................... 11
2.4 Classes ....................................... 12
2.4.1 Small .................................... 12
2.4.2 Cohesion .................................. 12
2.5 Refactoring ..................................... 12
2.6 State-Machines ................................... 12
2.7 HorizontalAlignment................................ 12
2.8 VerticalWhitespace................................. 12
3 Essential C ++ Knowledge 13
3.1 TypeConversion .................................. 13
3.1.1 ImplicitConversion............................. 13
3.1.2 ExplicitConversion............................. 14
3.2 Initialization..................................... 14
3.2.1 FundamentalTypes............................. 14
3.2.2 Arrays.................................... 15
3.2.3 ClassesandStructs............................. 15
3.3 Classes ....................................... 17
4 Multi-Threading 18
4.1 InstructionReordering ............................... 18
4.2 Statically Allocated Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
4.3 Inter-Task Communication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
4.4 Protecting Shared Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
4.5 Atomics....................................... 19
4.6 ThreadSafety.................................... 20
4.7 Reentrancy [3, Reentrancy (computing)] . . . . . . . . . . . . . . . . . . . . . . 20
4.8 UsageofModules.................................. 20
5 Debugging 21
6 Test-Driven Development 22
6.1 BenetsofTDD[4,p.32] ............................. 22
iii
7 Design Patterns 23
7.1 Single-InstanceModule............................... 23
7.2 Multiple-Instance Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
8 Code Smells 24
9 TouchGFX 25
Bibliography 26
iv
Foreword
This document presents a common coding style for our team. It also serves as a collection of
important programming concepts we all should know.
v
Chapter 1
Coding Style
1.1 Formatting
1.1.1 Module Files
A module is a self-contained part of a system that has a well-defined interface. Modules normally
coincide with compilation units.
1.1.1.1 Header
1#ifndef MODULE_H
2# d e f i n e MODULE_H
3
4// includes
5/ / f o rw a r d d e c l a r a t i o n s
6
7namespace optional_namespace
8{
9
10 / / e x p o r t e d t y p e d e f s
11 / / e x p o r t e d g l o b a l v a r i a b l e d e c l a r a t i o n s
12 / / e x p o r t e d f u n c t i o n d e c l a r a t i o n s
13 / / e x p o r t e d i n l i n e f u n c t i o n d e f i n i t i o n s
14
15 }/ / namespace o p t i o n a l _ n a m e s p a c e
16 # e n d i f / / MODULE_H
Listing 1.1: Module header format
1.1.1.2 Source
1// includes
2/ / f o rw a r d d e c l a r a t i o n s
3// using directives
4// using declarations
5
1
6// typedefs
7/ / s t a t i c f u n c t i o n d e c l a r a t i o n s
8/ / s t a t i c g l o b a l v a r i a b l e d e f i n i t i o n s
9/ / s t a t i c f u n c t i o n d e f i n i t i o n s
10
11 namespace optional_namespace
12 {
13
14 / / e x p o r t e d g l o b a l v a r i a b l e d e f i n i t i o n s
15 / / e x p o r t e d f u n c t i o n d e f i n i t i o n s
16
17 }/ / namespace o p t i o n a l _ n a m e s p a c e
Listing 1.2: Module source format
1.1.2 Class Files
Every class should have its own header and source file. The files give a convenient overview of all
the used classes. In contrast: having to scroll trough endless files to find class definitions is not so
convenient.
1.1.2.1 Header
1#ifndef CLASS_H
2# d e f i n e CLASS_H
3
4// includes
5/ / f o rw a r d d e c l a r a t i o n s
6
7namespace optional_namespace
8{
9
10 / / c l a s s d o c u m e n t a t i o n
11 class Class
12 {
13 public :
14 // public typedefs
15 / / p u b l i c s t a t i c member v a r i a b l e s
16 / / p u b l i c s t a t i c f u n c t i o n s
17 / / p u b l i c non s t a t i c member v a r i a b l e s
18 / / p u b l i c s p e c i a l member f u n c t i o n s
19 / / p u b l i c p u r e v i r t u a l f u n c t i o n s
20 / / p u b l i c v i r t u a l f u n c t i o n s
21 / / p u b l i c v i r t u a l o v e r r i d d e n f u n c t i o n s
22 / / p u b l i c v i r t u a l f i n a l f u n c t i o n s
23 / / p u b l i c non s t a t i c member f u n c t i o n s
24
25 protected :
26 / / s ee p u b l i c
27
28 private :
29 / / s ee p u b l i c
30
31 / / f r i e n d d e c l a r a t i o n s
2
32 }
33
34 }/ / namespace o p t i o n a l _ n a m e s p a c e
35 # e n d i f / / CLASS_H
Listing 1.3: Class header format
1.1.2.2 Source
1// includes
2/ / f o rw a r d d e c l a r a t i o n s
3// using directives
4// using declarations
5
6namespace optional_namespace
7{
8
9/ / s t a t i c member v a r i a b l e s
10 / / p u b l i c s t a t i c member f u n c t i o n s
11 / / s p e c i a l member f u n c t i o n s
12 / / v i r t u a l f u n c t i o n s
13 / / v i r t u a l o v e r r i d d e n f u n c t i o n s
14 / / v i r t u a l f i n a l f u n c t i o n s
15 / / non s t a t i c member f u n c t i o n s
16
17 }/ / namespace o p t i o n a l _ n a m e s p a c e
Listing 1.4: Class source format
1.1.3 Order of Includes [1]
1. Dir/File.h
2. A blank line
3. C system files
4. C++ system files
5. A blank line
6. Other libraries’ .h files
7. Your project’s .h files
1.1.4 Functions
1Re t ur n T y p e F unctionN a me ( Type par_name1 , Type par _ name2 ) ;
Listing 1.5: Function declaration format 1
1Re t ur n T y p e F unctionN a me (
2Type p ar_name1 , Type par_name2 , Type par_ n ame3 ) ;
Listing 1.6: Function declaration format 2
3
1Re t ur n T y p e F unctionN a me ( Type par_name1 ,
2Type par_name2 ,
3Type pa r _name3 ) ;
Listing 1.7: Function declaration format 3
1Re t ur n T y p e F unctionN a me (
2Type par_name1 ,
3Type par_name2 ,
4Type pa r _name3 ) ;
Listing 1.8: Function declaration format 4
Function definition and function call formatting is equivalent.
1.1.5 If statements
When a line is getting too long, prefer to put the logical AND and OR at the end of the line, instead
of on the next line.
1i f ( t h i s _ o n e _ t h i n g > t h i s _ o t h e r _ t h i n g &&
2a _ t h i r d _ t h i n g == a _ f o u r t h _ t h i n g )
3{
4/ / . . .
5}
6else
7{
8/ / . . .
9}
Listing 1.9: If statement formatting
1.1.6 Switch statements
Don’t indent the cases.
1switch ( v a r )
2{
3c a s e 0 :
4/ / . . .
5break ;
6
7c a s e 1 :
8/ / . . .
9break ;
10
11 default :
12 / / . . .
13 break ;
14 }
Listing 1.10: Swich statement formatting
4
1.1.7 Loops
In a for loop, make sure the comparison is done between variables of the same type. In the
following example, i and ARRAY_LENGTH are both of type uint8_t.
1s t a t i c c o n s t e x p r u i n t 8 _ t ARRAY_LENGTH = 1 0 ;
2f o r ( u i n t 8 _ t i = 0 ; i < ARRAY_LENGTH ; ++ i )
3{
4/ / . . .
5}
Listing 1.11: For loop formatting
1while ( v a r == true )
2{
3/ / . . .
4}
Listing 1.12: While loop formatting
1do
2{
3/ / . . .
4}
5while ( v a r < SOME_NUMBER) ;
Listing 1.13: Do while loop formatting
1.1.8 Array Initialization
1u i n t 8 _ t so me_numbers = { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } ;
Listing 1.14: Array initialization format 1
1c o n s t c h ar vegetable_names = {
2"carrots" ," c a u l i f l o w e r " ," p e a s " ," o n i o n s " ," spinach " ,"witloof"
3} ;
Listing 1.15: Array initialization format 2
1c o n s t c h ar fruit_names = {"apple" ,
2" o r a n g e " ,
3" s t r a w b e r r y " } ;
Listing 1.16: Array initialization format 3
1c o n s t c h ar array_of_long_strings = {
2" I f t h e e l e m e n t s o f t h e a r r a y a r e v e ry l o ng " ,
3" we can p u t e v e r y e l e m e n t on a d i f f e r e n t l i n e l i k e t h i s
4} ;
Listing 1.17: Array initialization format 4
5
1.1.9 Preprocessor Directives
Preprocessor directives are normally placed one indent to the left.
1class MyClass
2{
3v o i d Function ()
4{
5# i f DEBUGGING == 1
6D e b u g P r i n t ( ) ;
7# e n d i f
8}
9}
Listing 1.18: Preprocessor directive indentation
Indentation of preprocessor directives is allowed.
1# i f DEBUGGING == 1
2# i f d e f i n e d ( PIC18F2480 )
3# d e f i n e FLASH_MEMORY_END 0x3DC0
4#else
5# e r r o r " Can t s e t up f l a s h memory end ! "
6# e n d i f
7#else
8# i f d e f i n e d ( PIC18F2480 )
9# d e f i n e FLASH_MEMORY_END 0 x400 0
10 #else
11 # e r r o r " Can t s e t up f l a s h memory end ! "
12 # e n d i f
13 # e n d i f
Listing 1.19: Preprocessor directive indentation
1.2 Initialization
1.2.1 Fundamental Types
1uint8_t uninitialized_var ; / / d e f a u l t i n t i a l i z a t i o n
2u i n t 8 _ t z e r o e d _ v a r 1 ( 0 ) ; // direct initialization
3u i n t 8 _ t z e r o e d _ v a r 2 = 0 ; / / copy i n i t i a l i z a t i o n => p r e f e r r e d
4u i n t 8 _ t z e r o e d _ v a r 3 { 0 } ; / / d i r e c t l i s t initialization
5u i n t 8 _ t z e r o e d _ v a r 4 = { 0 }; / / copyl i s t initialization
Listing 1.20: Initialization of fundamental type objects
This style guide prefers copy initialization for fundamental type objects because it also works in
C, it’s what we are accustomed to.
Note that for fundamental types default initialization actually means uninitialized.
By leaving the parentheses empty, objects are value initialized. For fundamental types this means
that all bits are made zero.
6
1u i n t 8 _ t z e r o e d _ v a r 5 ( ) ;
2u i n t 8 _ t z e r o e d _ v a r 6 { } ;
3u i n t 8 _ t z e r o e d _ v a r 7 = { } ;
Listing 1.21: Value initialization of fundamental type objects
1.2.2 Arrays
1u i n t 8 _ t u n i n i t i a l i z e d _ a r r a y [ 3 ] ; // default initialization
2u i n t 8 _ t a r r a y 1 [ 3 ] { 0 , 1 , 2 } ; / / d i r e c t list initialization
3u i n t 8 _ t a r r a y 2 [ 3 ] = { 0 , 1 , 2 }; / / copylist i n i t i a l i z a t i o n => p r e f e r r e d
Listing 1.22: Initialization of arrays
Default initialization of an array results in default initialization of every element.
This style guide prefers copy-list-initialization for arrays over direct-list-initialization because it
also works in C.
Array1 and array2 are initialized using aggregate initialization (a form of list initialization). The
individual array elements are initialized by copy-initialization from the initializers specified in the
braced-init-list. If the number of initializer clauses is less than the number of elements, or the
initializer list is completely empty, the remaining elements are value initialized.
1u i n t 8 _ t z e r o e d _ a r r a y 1 [ 3 ] { } ;
2u i n t 8 _ t z e r o e d _ a r r a y 2 [ 3 ] = { } ;
Listing 1.23: Value initialization of arrays
The preferred way of zeroing an array is shown in listing 3.5.
1u i n t 8 _ t z e r o e d _ a r r a y 3 [ 3 ] = { 0 } ;
Listing 1.24: Zeroing arrays
1.2.3 Classes and Structs
1.2.3.1 Aggregates
A class type is an aggregate if it has
no private or protected non-static data members;
no user-declared or inherited constructors;
no virtual, private, or protected base classes;
no virtual member functions.
Informall: a class type is an aggregate if it looks like the good old structs that we know from C.
7
1struct GoodOldCStruct
2{
3u i n t 3 2 _ t a n _ i n t e g e r ;
4f l o a t a_float ;
5}
6
7Go o d O ld C S tr u c t u n i n i t i a l i z e d _ s t r u c t // default initialization
8Go o d Ol dC St ru ct s t r u c t 1 { 10 , 3 . 1 4 } ; / / d i r e c t listinitialization
9Go o d Ol dC St ru ct s t r u c t 2 = { 10 , 3 . 1 4 } ; / / copyl i s t i n i t i a l i z a t i o n => p r e f e r r e d
10 Go o d O ld C S tr u c t s t r u c t 3 ( s t r u c t 1 ) ; // direct initialization
11 Go o d O ld C S tr u c t s t r u c t 4 = s t r u c t 1 ; / / cop y i n i t i a l i z a t i o n => p r e f e r r e d
12 Go o d O ld C S tr u c t s t r u c t 5 { s t r u c t 1 } ; / / d i r e c t l i s t initialization
13 Go o d O ld C S tr u c t s t r u c t 6 = { s t r u c t 1 } ; / / copyl i s t initialization
Listing 1.25: Initialization of aggregates
Default initialization of an aggregate results in default initialization of every non-static member.
When using aggregate initialization (struct1 and struct2), this guide prefers copy-list-initialization
over direct-list-initialization because it also works in C. Each non-static class member, in order
of appearance in the class definition, is copy-initialized from the corresponding clause of the
initializer list. If the number of initializer clauses is less than the number of members or the
initializer list is completely empty, the remaining members are value-initialized.
When initializing from another struct (struct3, struct4, struct5 and struct6), this guide prefers copy
initialization (struct4).
The preferred way of zeroing aggregate types is shown in listing 3.7.
1Go o d OldCSt r uc t z e r o e d _ a g g r e g a t e = { 0 } ;
Listing 1.26: Zeroing aggregates
1.2.4 Classes
1.2.5 Templates
1.3 Naming Conventions
1.3.1 Arrays
The name of an array should make it clear that it contains multiple objects, for example by making
the name a plural noun.
1.3.2 Enums
Names of enums should be a singular noun because the enum name is the type of the enum values,
e.g. RED is a value of type Color.
The enumerators are constant expressions, therefore they are all caps.
8
1enum Color
2{
3RED,
4GREEN,
5BLUE
6}
Listing 1.27: Enum naming convention
1.4 Miscellaneous
Do not put private static member variables in the class definition (in the header file). Make them
static global variables in the .cpp file.
Use enum typedefs to give related constants a specific type.
An RTOS is only needed if there are long tasks. Long tasks can block short tasks, and so they can
make the short tasks feel unresponsive.
When referring to the number of elements in a collection, prefer the term count over size.
Prefer #if over #ifdef.
Functions should only do what their name says they do.
Namespaces should start with a small letter to distinguish them from classes.
https://gist.github.com/lefticus/10191322
9
Chapter 2
Clean Code
Figure 2.1: The only valid measurement of code quality: WTFs / minute [5]
This chapter contains guidelines for writing clean code. I made a separate chapter for this because
these guidelines are universal, it does not only apply to PsiControl style..
2.1 Performance and Safety
2.1.1 Comparisons
Comparisons should be done on variables of the same type. The usual arithmetic conversions (see
3.1.1.2) can convert an int to an unsigned int, hereby turning small negative integers into large
positive integers.
1#i n c l u d e < s t d i o . h>
2#i n c l u d e < s t d i n t . h>
3
4s t a t i c c o n s t e x p r u i n t 8 _ t ARRAY_LENGTH = 5 ;
5
6int main ( )
10
7{
8i n t 3 2 _ t i n t _ a r r a y [ARRAY_LENGTH] = { 0 , 1 , 5, 6 , 9 } ;
9
10 f o r ( u i n t 8 _ t i = 0 ; i < ARRAY_LENGTH ; ++ i )
11 {
12 u i n t 3 2 _ t t e n = 1 0;
13
14 c o n s t c h ar l a r g e r _ t h a n _ 1 0 = i n t _ a r r a y [ i ] > t e n ? " t r u e " :" f a l s e " ;
15 printf ("%2d > 10 = %s \ n " , i n t _ a r r a y [ i ] , l a r g e r _ t h a n _ 1 0 ) ;
16 }
17
18 g e t c h a r ( ) ;
19 }
Listing 2.1: Comparing different types: code
Notice that i and ARRAY_LENGTH, which are compared in the for loop condition, have the same
type: uint8_t.
0 > 10 = f a l s e
1 > 10 = f a l s e
5 > 10 = true
6 > 10 = f a l s e
9 > 10 = f a l s e
Listing 2.2: Comparing different types: output
2.2 Variables
Starting the name of a boolean member variable with is can improve readability, e.g. this->is_initialized.
2.3 Functions
Functions should be small. They should not be longer than 20 lines. If a function does several
distinct things, place these distinct subroutines in separate functions, the added abstraction will
make the code easier to read.
The interface functions of a software module should check their arguments for valid input. These
checks shouldn’t be assertions (they freeze your program) tough, as you can’t know what argu-
ments the calling code will use.
Prefer to enumerate a list of functions than to pass an enumerated type. It’s safer and more de-
scriptive [4].
Starting the name of a member function returning bool with Is can improve readability, e.g.
my_module.IsInitialized().
11
2.4 Classes
2.4.1 Small
Classes should be small. Group related variables and member functions of a large class in a
separate class. Subdividing code in small classes and modules makes the structure of the software
clearer.
2.4.2 Cohesion
Cohesion is a measurement of the connection between member variables and member functions. If
all member functions use all member variables, the class has maximum cohesion. Classes should
be as cohesive as possible. A class with low cohesion should probably be split in separate classes.
2.5 Refactoring
Refactoring is the activity of changing a program’s structure without changing its behavior. The
purpose is to make less work by creating code that is easy to understand, easy to evolve, and easy
to maintain by others and ourselves.
Small messes are easy to create. Unfortunately, they are also easy to ignore. The mess will never
be easier to clean up than right after, ahem, you make it. Clean the mess while it’s fresh.
2.6 State-Machines
Functions for which the output does not solely depend on the input, depend on a state. Implicit
states, e.g. using bool variables, can make the code complex. Prefer to make the states explicit by
defining an enum with the different states and implementing a state machine.
State machines can be especially useful in periodic tasks and TouchGfx handleTickEvent() func-
tions.
2.7 Horizontal Alignment
Don’t over-use horizontal alignment. One noteworthy case where horizontal alignment benefits
readability is in the definition of enums.
2.8 Vertical Whitespace
Use blank lines sparingly. Blank lines can be used to visually group related lines of code. Do not
put a blank line after every line of code if you think that all the lines are unrelated!
12
Chapter 3
Essential C ++ Knowledge
3.1 Type Conversion
3.1.1 Implicit Conversion
3.1.1.1 Integral Promotion [2, Implicit conversions]
Arithmetic operators do not accept types smaller than int as arguments, and integral promotions
are automatically applied after lvalue-to-rvalue conversion 1, if applicable. This conversion always
preserves the value.
The following implicit conversions are classified as integral promotions:
signed char or signed short can be converted to int;
unsigned char or unsigned short can be converted to int if it can hold its entire value range,
and unsigned int otherwise;
char can be converted to int or unsigned int depending on the underlying type: signed char or
unsigned char (see above);
wchar_t, char16_t, and char32_t can be converted to the first type from the following list able
to hold their entire value range: int, unsigned int, long, unsigned long, long long, unsigned
long long;
an unscoped enumeration type whose underlying type is not fixed can be converted to the
first type from the following list able to hold their entire value range: int, unsigned int, long,
unsigned long, long long, or unsigned long long, extended integer types (in size order, signed
given preference over unsigned). If the value range is greater, no integral promotions apply;
an unscoped enumeration type whose underlying type is fixed can be converted to its under-
lying type, and, if the underlying type is also subject to integral promotion, to the promoted
underlying type. Conversion to the unpromoted underlying type is better for the purposes of
overload resolution;
a bit field type can be converted to int if it can represent entire value range of the bit field,
otherwise to unsigned int if it can represent entire value range of the bit field, otherwise no
integral promotions apply;
the type bool can be converted to int with the value false becoming 0 and true becoming 1.
1lvalue-to-rvalue conversion models the act of reading a value from a memory location into a CPU register.
13
3.1.1.2 Usual Arithmetic Conversions [2, Arithmetic operators]
If the operand passed to an arithmetic operator is integral or unscoped enumeration type, then
before any other action (but after lvalue-to-rvalue conversion, if applicable), the operand undergoes
integral promotion. If an operand has array or function type, array-to-pointer and function-to-
pointer conversions are applied.
For the binary operators (except shifts), if the promoted operands have different types, additional
set of implicit conversions is applied, known as usual arithmetic conversions with the goal to
produce the common type (also accessible via the std::common_type type trait).
If either operand has scoped enumeration type, no conversion is performed: the other operand
and the return type must have the same type.
Otherwise, if either operand is long double, the other operand is converted to long double.
Otherwise, if either operand is double, the other operand is converted to double.
Otherwise, if either operand is float, the other operand is converted to float.
Otherwise, the operand has integer type (because bool, char, char16_t, char32_t, wchar_t, and
unscoped enumeration were promoted at this point) and integral conversions are applied to
produce the common type, as follows:
If both operands are signed or both are unsigned, the operand with lesser conversion rank
is converted to the operand with the greater integer conversion rank.
Otherwise, if the unsigned operand’s conversion rank is greater or equal to the conversion
rank of the signed operand, the signed operand is converted to the unsigned operand’s type.
Otherwise, if the signed operand’s type can represent all values of the unsigned operand,
the unsigned operand is converted to the signed operand’s type.
Otherwise, both operands are converted to the unsigned counterpart of the signed operand’s
type.
The conversion rank above increases in order bool, signed char, short, int, long, long long. The
rank of any unsigned type is equal to the rank of the corresponding signed type. The rank of char is
equal to the rank of signed char and unsigned char. The ranks of char16_t, char32_t, and wchar_t
are equal to the ranks of their underlying types.
3.1.2 Explicit Conversion
3.2 Initialization
3.2.1 Fundamental Types
1uint8_t uninitialized_var ; / / d e f a u l t i n t i a l i z a t i o n
2u i n t 8 _ t z e r o e d _ v a r 1 ( 0 ) ; // direct initialization
3u i n t 8 _ t z e r o e d _ v a r 2 = 0 ; / / copy i n i t i a l i z a t i o n => p r e f e r r e d
4u i n t 8 _ t z e r o e d _ v a r 3 { 0 } ; / / d i r e c t l i s t initialization
5u i n t 8 _ t z e r o e d _ v a r 4 = { 0 }; / / copyl i s t initialization
Listing 3.1: Initialization of fundamental type objects
Default initialization of a fundamental types actually leaves the object uninitialized.
14
This style guide prefers copy initialization for fundamental type objects because it also works in
C, it’s what we are accustomed to.
By leaving the parentheses empty, objects are value initialized. For fundamental types this means
that all bits are made zero.
1u i n t 8 _ t z e r o e d _ v a r 5 ( ) ;
2u i n t 8 _ t z e r o e d _ v a r 6 { } ;
3u i n t 8 _ t z e r o e d _ v a r 7 = { } ;
Listing 3.2: Value initialization of fundamental type objects
3.2.2 Arrays
1u i n t 8 _ t u n i n i t i a l i z e d _ a r r a y [ 3 ] ; // default initialization
2u i n t 8 _ t a r r a y 1 [ 3 ] { 0 , 1 , 2 } ; / / d i r e c t list initialization
3u i n t 8 _ t a r r a y 2 [ 3 ] = { 0 , 1 , 2 }; / / copylist i n i t i a l i z a t i o n => p r e f e r r e d
Listing 3.3: Initialization of arrays
Default initialization of an array results in default initialization of every element.
This style guide prefers copy-list-initialization for arrays over direct-list-initialization because it
also works in C.
Array1 and array2 are initialized using aggregate initialization (a form of list initialization). The
individual array elements are initialized by copy-initialization from the initializers specified in the
braced-init-list. If the number of initializer clauses is less than the number of elements, or the
initializer list is completely empty, the remaining elements are value initialized.
1u i n t 8 _ t z e r o e d _ a r r a y 1 [ 3 ] { } ;
2u i n t 8 _ t z e r o e d _ a r r a y 2 [ 3 ] = { } ;
Listing 3.4: Value initialization of arrays
The preferred way of zeroing an array is shown in listing 3.5.
1u i n t 8 _ t z e r o e d _ a r r a y 3 [ 3 ] = { 0 } ;
Listing 3.5: Zeroing arrays
3.2.3 Classes and Structs
3.2.3.1 Aggregates
A class type is an aggregate if it has
no private or protected non-static data members;
no user-declared or inherited constructors;
no virtual, private, or protected base classes;
15
no virtual member functions.
Informally: a class type is an aggregate if it looks like the good old structs that we know from C.
1struct GoodOldCStruct
2{
3u i n t 3 2 _ t a n _ i n t e g e r ;
4f l o a t a_float ;
5}
6
7Go o d O ld C S tr u c t u n i n i t i a l i z e d _ s t r u c t // default initialization
8Go o d Ol dC St ru ct s t r u c t 1 { 10 , 3 . 1 4 } ; / / d i r e c t listinitialization
9Go o d Ol dC St ru ct s t r u c t 2 = { 10 , 3 . 1 4 } ; / / copyl i s t i n i t i a l i z a t i o n => p r e f e r r e d
10 Go o d O ld C S tr u c t s t r u c t 3 ( s t r u c t 1 ) ; // direct initialization
11 Go o d O ld C S tr u c t s t r u c t 4 = s t r u c t 1 ; / / cop y i n i t i a l i z a t i o n => p r e f e r r e d
12 Go o d O ld C S tr u c t s t r u c t 5 { s t r u c t 1 } ; / / d i r e c t l i s t initialization
13 Go o d O ld C S tr u c t s t r u c t 6 = { s t r u c t 1 } ; / / copyl i s t initialization
Listing 3.6: Initialization of aggregates
Default initialization of an aggregate results in default initialization of every non-static member.
When using aggregate initialization (struct1 and struct2), this guide prefers copy-list-initialization
over direct-list-initialization because it also works in C. Each non-static class member, in order
of appearance in the class definition, is copy-initialized from the corresponding clause of the
initializer list. If the number of initializer clauses is less than the number of members or the
initializer list is completely empty, the remaining members are value-initialized.
When initializing from another struct object (struct3, struct4, struct5 and struct6), this guide
prefers copy initialization (struct4).
The preferred way of zeroing aggregate types is shown in listing 3.7.
1Go o d OldCSt r uc t z e r o e d _ a g g r e g a t e = { 0 } ;
Listing 3.7: Zeroing aggregates
3.2.3.2 Non-Aggregate Classes
1class Rectangle
2{
3public :
4R e c t a n g l e ( i n t 3 2 _ t width , i n t 3 2 _ t h e i g h t ) ;
5int Are a ( v o i d ) ;
6
7private :
8i n t 3 2 _ t w i dth
9i n t 3 2 _ t h e i g h t ;
10 } ;
11
12 Rectangle uninitialized_rect ; // default initialization
13 R e c t a n g l e r e c t 1 ( 1 0 , 5 ) ; / / d i r e c t i n i t i a l i z a t i o n => p r e f e r r e d
14 R e c t a n g l e r e c t 2 {1 0 , 5 } ; / / d i r e c t list initialization
15 R e c t a n g l e r e c t 3 = {1 0 , 5 } ; / / copyl i s t initialization
16
16 R e c t a n g l e r e c t 4 ( r e c t 1 ) ; // direct initialization
17 R e c t a n g l e r e c t 5 = r e c t 1 ; / / copy i n i t i a l i z a t i o n => p r e f e r r e d
18 R e c t a n g l e r e c t 6 { r e c t 1 } ; / / d i r e c t listinitialization
19 R e c t a n g l e r e c t 7 = { r e c t 1 } ; / / copyl i s t initialization
Listing 3.8: Initialization of class objects
The uninitialized_rect is initialized using default initialization. Default initialization calls the de-
fault constructor (constructor with no parameters), but since there is no default constructor, the
struct is left uninitialized. A default constructor that inits the width and height to zero could be
added to avoid uninitialized Rectangles.
Rect1, rect2 and rect3 are all initialized using the constructor. Direct initialization is preferred
because it visually indicates that a constructor (= function) is called.
When initializing from another class object (struct4, struct5, struct6 and struct7), this guide prefers
copy initialization (struct5).
3.3 Classes
A class is a user-defined type that can be viewed as:
acollection of variables, with functions that can access these variables;
acollection of functions, with variables that can be accessed by all of these functions.
17
Chapter 4
Multi-Threading
When writing software modules that need to be accessed from different threads, extreme care
must be taken to ensure thread safety; otherwise data races can occur. An ISR can also be seen as
a different thread of execution, and therefore the same rules apply to ISRs.
4.1 Instruction Reordering
1b o o l initialized ;
2u i n t 3 2 _ t number ;
3
4v o i d thread1 ( v o i d )
5{
6/ / . . . / / i n s t r u c t i o n r e o r d e r i n g can do t h i s :
7nu mb er = 6 6 6 ; / / i n i t i a l i z e d = t r u e ;
8initialized = true ;/ / n um be r = 6 6 6 ;
9/ / . . .
10 }
11
12 v o i d thread2 ( v o i d )
13 {
14 / / . . .
15 i f ( intialized )
16 {
17 Use ( number )
18 }
19 / / . . .
20 }
Listing 4.1: Instruction reordering
You might have thought that no critical section is needed inside thread1 because a store to an
aligned variable (smaller or equal to a word) is an atomic operation. Wrong! The compiler can
reorder these instructions so that initialized becomes true before number gets initialized! And it
gets worse. The code generated by the compiler can even be executed in a different order by the
hardware. Special instructions called memory barriers exist to prohibit instruction reordering. So
the need for a critical section here is certain. Let’s hope your RTOS calls the memory barrier
instructions when handling mutexes.
For your information, the C++ standard library has 2 memory barrier functions:
18
Compiler memory barrier: std::atomic_signal_fence(std::memory_order order)
Hardware memory barrier: std::atomic_thread_fence(std::memory_order order)
std::atomic_signal_fence is equivalent to std::atomic_thread_fence, except no CPU instructions
for memory ordering are issued. Only reordering of the instructions by the compiler is suppressed.
4.2 Statically Allocated Memory
Unprotected statically allocated (non-const) memory is not thread safe. The handling of these
variables needs to be done in a critical section (mutex or disabling IRQs). This does not apply to
constants as their value never changes.
Examples of statically allocated memory:
Global variables (extern and static): If the module is used in different threads, the global vari-
ables need to be handled inside a critical section.
Static member variables: Try to avoid these. If really needed, like when implementing a single-
ton, put them inside a critical section.
Static local variables: Try to avoid these. When inside a member fucntion: check if the local
variable needs to be static, maybe it can/should be a data member of the class?
4.3 Inter-Task Communication
Use a queue.
4.4 Protecting Shared Resources
Shared resources (e.g. memory) need to be protected by mutexes.
4.5 Atomics
Normally, a store to and a load from an aligned variable (smaller or equal to a word) are atomic
operations. But this is not guaranteed on all CPU architectures. Use std::atomic to make the com-
piler enforce atomic operations. Furthermore, the compiler will then guarantee that all instructions
before the atomic operation really are executed before the atomic operation; and that all instruc-
tions after the atomic operation really are executed after the atomic operation. This precludes the
need for a critical section.
19
4.6 Thread Safety
4.7 Reentrancy [3, Reentrancy (computing)]
4.8 Usage of Modules
Modules that are used in different threads need to make their API functions thread safe by
using critical sections (or atomics) to protect shared data; or
using a "module only thread", the API functions then need to send a message to the task (e.g.
with a queue) to execute something.
If modules may only be used in one thread, maybe this should be made clear in the module name?
20
Chapter 5
Debugging
Before trying to fix a bug, you should have a thorough understanding of the problem.
21
Chapter 6
Test-Driven Development
6.1 Benefits of TDD [4, p. 32]
Fewer bugs
Small and large logic errors, which can have grave consequences in the field, are found
quickly during TDD. Defects are prevented.
Less debug time
Having fewer bugs means less debug time. That’s only logical, Mr. Spock.
Fewer side effect defects
Tests capture assumptions, constraints, and illustrate representative usage. When new code
violates a constraint or assumption, the tests holler.
Documentation that does not lie
Well-structured tests become a form of executable and unambiguous documentation. A work-
ing example is worth 1,000 words.
Peace of mind
Having thoroughly tested code with a comprehensive regression test suite gives confidence.
TDD developers report better sleep patterns and fewer interrupted weekends.
Improved design
A good design is a testable design. Long functions, tight coupling, and complex conditionals
all lead to more complex and less testable code. The developer gets an early warning of design
problems if tests cannot be written for the envisioned code change. TDD is a code-rot radar.
Progress monitor
The tests keep track of exactly what is working and how much work is done. It gives you
another thing to estimate and a good definition of done.
Fun and rewarding
TDD is instant gratification for developers. Every time you code, you get something done,
and you know it works.
22
Chapter 7
Design Patterns
7.1 Single-Instance Module
An Abstract Data Type.
[4, p. 51]
7.2 Multiple-Instance Module
An Abstract Data Type.
[4, p. 51]
23
Chapter 8
Code Smells
A code smell is an indication of bad coding style which you should develop a nose for.
Reference and pointer parameters should be declared as pointing to const if the pointed-to-object
doesn’t have to be changed.
Pointers are dangerous, they should be avoided. Try to use references instead.
Do not return NULL pointers to indicate an error. They can become the source of bugs.
Built-in type member variables do not get initialized by default. It is better to initialize them
always.
24
Chapter 9
TouchGFX
Keep status variables in the presenter. Access them from widgets in the view.
25
Bibliography
[1] Google C++ Style Guide. https://google.github.io/styleguide/cppguide.html.
[2] C++ reference. https://en.cppreference.com/w/, 2018.
[3] Wikipedia. https://en.wikipedia.org/wiki/Main_Page, 2018.
[4] James W. Grenning. Test-Driven Development for Embedded C. The Pragmatic Bookshelf,
2011.
[5] Robert C. Martin. Clean Code. Prentice Hall, 2009.
26

Navigation menu