C++ code style.
#define AND && #define OR || #define EQ == I have done my best to follow consistent C/C++ code style in all source code examples.
In this book I have not placed curly brackets on a separate line. Please, do not send me hate mail. The only reason is to make the code snippets shorter. Shorter code has a better chance of fitting in the smaller e-reader displays. There are no comments in the code itself for the same reason. Hopefully, the lack of comments has been compensated for by the interspersed explanations.
I am using the camelcase name convention. Types and class names begin with uppercase and variables begin with lower case, while constants are all upper case with the underscore delimiter.
Software primitives.
Mutual exclusion.
The great thing about Object Oriented code is that it can make small, simple problems look like large, complex ones. In the multi-task environment, threads and interrupts can concurrently read and write data objects. I can use different tools to synchronize access to the data between different contexts and create thread safe APIs. Among the available tools are semaphores and mutual exclusion APIs provided by operating systems, as well as disabling all interrupts, disabling some interrupts, disabling the operating system scheduler, and using spin locks.
When I write a wrapper around an API provided by my real-time operating system or by the hardware, I want the wrapper to be as thin as possible. Usually, I measure the overhead of the wrapper using a number of assembly instructions. The number of instructions provides a good approximation of the CPU cycles or execution time. I am starting with a code snippet that creates and calls a dummy lock object. class LockDummy { public : LockDummy() { cout << "Locked context" << endl; } ~LockDummy() { cout << "Lock is freed" << endl; } }; In the following usage example the C++ specifier auto tells to the compiler to automatically supply the correct type the C++11 compiler knows to deduct certain types of variables in some situations. For example, the C++ compiler can figure out the return type of the left side of an assignment or the return type of a function.
In the function main() compiler will call the output function two times and will not add any other code. The lock is released the destructor ~LockDummy gets called when the scope of the variable myDummyLock ends. The scope could be a while loop. I do not have to call unlock before each and every return from the function. The C++ compiler makes sure that the destructor is always called and called exactly once (see more about RAII in [1]). This convenient service comes without any performance overhead.
The output of the following code is going to be Locked context Protected context Lock is freed int testDummyLock () { #if (__cplusplus >= 201103) // use "auto" if C++11 or better auto myDummyLock = LockDummySimple (); #else LockDummySimple myDummyLock = LockDummySimple(); #endif cout << "Protected context" << endl ; return 0; } I want to stress this idea using another example. In the following code, I set a scope around the declaration of the lock variable. The output of the code is going to be Locked context Protected context Lock is freed End of main int main() { { LockDummy lock; cout << "Protected context" << endl ; } cout << "End of main" << endl; return 0; } My second lock is a more realistic one, and it disables a hardware interrupt. I assume that there is an API that disables and enables an interrupt. I am refactoring the original lock code a little bit. First of all, I use a template, which is a feature of C++ that allows you to declare a class that operates with generic types.
I am going to reuse the code in the template Lock for different synchronization objects. The code in the template Lock can manipulate any structure or class that implements two public methods: static methods get() and release(). I carefully avoid polymorphism here, I do not require synchronization objects to belong to the same hierarchy of classes. The wrapper around the disable/enable interrupts API, which likely writes to the hardware, is not going to be a child/friend/parent/relative/derivation of a wrapper for the operating system semaphore. This is what the API that disables and enables interrupts looks like: static inline void interruptDisable ( void ) { cout << "Disable" << endl ; } static inline void interruptEnable ( void ) { cout << "Enable" << endl ; } I am describing a synchronization object. The class implements two methods: get and release.
Both methods are inline, which helps the optimizer to decide if calls to the methods should be substituted by the methods code. The end result is going to be similar to using a macro definition in C. The default constructor in the class SynchroObject is private, I do not want any objects of this type in the application. My C++ compiler so far has not added any object code to my executable file. class SynchroObject { SynchroObject () {}; public : static inline void get () { interruptDisable(); } static inline void release () { interruptEnable(); } }; The template class Lock can manipulate any type of synchronization objects that provides a get/release API. All methods of the template class Lock are inline.
The C++ compiler is not going to add any functions to the object code, but will rather replace the calls to the Lock methods with the code of the get/release from the synchronization object. template < typename Mutex > class Lock { public : inline Lock () { Mutex ::get(); } inline ~Lock () { Mutex ::release(); } }; Declare a new type MyLock that uses my SynchroObject to disable/enable interrupts. There is still no additional data or code in my executable except for the calls to the interrupt enable and interrupt disable API. typedef Lock < SynchroObject > MyLock ; Output of the function main() is going to be two words: Disable, Enable int main () { { MyLock lock ; } return 0; } Overhead of the wrapper around the functions that disable and enable interrupts is exactly zero. Indeed, if I check the disassembly, I will see two calls to the print function in the main routine and nothing else. I have written some C++ code which gets optimized to nothing.