Welcome to the Modern Embedded Systems Programming course. My name is Miro Samek and in this lesson I'd like to introduce object-oriented programming, which is an important stepping stone in understanding modern software of any kind, not just modern embedded programming. Today you will learn about the high-level concepts, but as usual in this video course, you will also see how object oriented programming works at the low-level inside your embedded microcontroller. This lesson lays the foundation for understanding most modern trends in programming and almost all upcoming lessons in this course will in one way or another rely on the concepts introduced today. As usual, let's get started by making a copy of the previous lesson 28 directory and renaming it to lesson 29. Get inside the new lesson 29 directory and double-click on the uVision project "lesson" to open it. To remind you quickly what happened so far, in the last seven lessons you learned about the Real-Time Operating System (RTOS) and the associated programming paradigm based on blocking and sharing of resources among concurrent threads, such as you blinky1, blinky2 and blinky3. Indeed, the most important and complex parts of the RTOS turned out not to be the context switching and thread scheduling, but rather the numerous mechanisms for blocking, synchronization, and mutual exclusion to support the "shared-state concurrency" programming paradigm. From the historic perspective, the RTOS went mainstream in the 1980’s, and the RTOS-based shared-state concurrency model with blocking, essentially unchanged, continues to be in the mainstream use to this day. However, during the 1980’s other important trends went mainstream as well, and these include object-oriented programming and event-driven programming. And since all these trends are essential to understanding modern embedded software, today I will explain the main concepts behind object-oriented programming, while I promise to talk about event- driven programming in the future. Many people think that object-oriented programming--OOP must necessarily be done in an OOP programming language, such as C++, Java, or Python. But actually, OOP is not the use of any specific language, but rather a way of software design based on the three fundamental concepts of: Encapsulation Inheritance; and Polymorphism Today you will see how to implement these concepts in standard, portable C. After that, you will see the exact same design implemented in C++, which will allow you to compare C to C++ and also to get a taste of programming in C++. So, let’s start with with the concept of Encapsulation, which is closely related to Information Hiding and Abstraction. You have actually already used some of these concepts in the design of the Board Support Package (BSP). Here, you have specifically separated the “What” needs to be done in the bsp.h header file, from “How” it is done in the bsp.c implementation file. You have used the concept of abstraction, because all the details of handling the LEDs on your TivaC LaunchPad board are abstracted away and simplified into just three operations: on, off, and toggle. You have also used the concept of information hiding, because the information about specific ways of performing these operations on the LEDs is hidden from the users of the LEDs, such as your blinky threads. All the users of the LEDs see and all they really need is the bsp.h header file. But thus far the design based on abstraction and information hiding within the BSP module has an important limitation, which is that the design handles only a fixed number of LEDs: red, blue, and green in this case, and it cannot be easily extended to handle an open-ended number of LEDs. For example, imagine that your project uses a graphic LCD display, on which you need to draw various shapes, such as rectangles, circles, and triangles. It is very hard to tell upfront how many of these shapes you would need, so it is difficult to hide all this in a module without resorting to some kind of dynamic memory allocation, which is generally a BAD idea in real-time and embedded systems. Instead, you need to give the application programmers a chance to allocate as many shapes as they like in any way they like: statically, automatically inside functions, or even dynamically on the heap; If they choose to do so. You can achieve this by creating a different Shape interface than the BSP.h. Inside the new shape.h header file, you represent the data needed by each Shape as a C structure, which then can be instantiated in any way you want. For example, every Shape has a specific position on the screen given by the x and y coordinates. Since the LCD screen is small, the 16-bit integer provides adequate dynamic range, but let’s allow negative (that is signed values) to represent Shapes that are off screen. So far all this is really noting new compared to lesson 12 of this course, which introduced C structures and typedefs. But now, comes the encapsulation part: By a coding convention, you disallow accessing the members of the Shape struct directly and instead you provide a set of functions specialized to work with the Shape structure: For example, you provide a way of initializing a Shape, called the “constructor”. This function takes a pointer to the Shape structure as the first parameter, which by convention you will call “me”. Since the “me” pointer should always point to the same Shape instance, let’s prevent changing it by making it const. Please note that the const is after the STAR, which means that the “me” pointer cannot change, but the Shape instance pointed to by the pointer IS allowed to change, which is the main purpose of the “constructor”. Next, you can also provide a function to move a shape on the screen by a given dx and dy increments, And one more function with a return value to provide the distance from the “me” Shape to another Shape ‘other’. This other shape should not need to change, so let’s make it const. Please note that now the const is before the STAR. But actually, the “me” Shape should not need to change either, so let’s make it const as well. Again, by adding const before the STAR. Please note that you establish the association between the Shape data and the Shape functions by means of the following two coding conventions: First, the names of all associated functions start with the name of the associated structure. And second, all these functions take the “me” pointer as the first parameter, with the purpose to specify which specific Shape instance the function is operating on. As you will see later in this lesson, the “me” pointer corresponds to the implicit “this” pointer in C++, and the “self” variable in Python, if you are familiar with Python. Now, you can add the C file shape.c and provide there the implementation of the Shape functions prototyped in shape.h. The “constructor” performs the initialization of the “me” structure from the provided x0 and y0 parameters. The move-by function modifies the position of the “me” Shape by adding the provided dx and dy offsets. And finally, the distance-from function calculates the distance between the “me” Shape and the “other” Shape. This function uses the simple “taxicab” geometry, which is adequate for Shapes but does not require extracting square root as the standard Cartesian metric. So, all this is quite straightforward, isn’t it? But what you just crated is called a Shape *CLASS*, which is quite a fundamental concept in object-oriented programming. A CLASS combines both data, which are called in OOP “attributes” and functions, which are called in OOP “operations” into one entity – the CLASS. One nice aspect of classes is that they can be drawn in class diagrams, which show a class as a box with the class name at the top, then attributes, and then operations. Later, when you have more classes, a class diagram can also show the relationships among classes. A class realizes the concept of encapsulation, because it presents to the outside only the outer shell in form of the operations, while the internal data and internal implementation remain encapsulated inside the shell. But back to the code, you can think of a class as a cookie-cutter from which you can create any number of instances of the class, called *OBJECTS*. So, now let’s include the shape.h class declaration in the main.c file and create a couple of such objects of the Shape class. First, you can allocate a Shape object s1 statically. You can also allocate another Shape object s2 automatically on the stack, inside a function, such as main(). And finally, you can also allocate yet another Shape object dynamically on the heap using the malloc() function from the standard library “stdlib.h”. As always with dynamic memory, it is also a good idea to immediately add the explicit free() library call for this object to prevent a memory leak. As I already mentioned, dynamic memory allocation is typically a rather BAD idea in real-time embedded software, but it is a possibility for instantiating classes, so I show it here only for completeness. But to finish this point, since you are using dynamic memory now, you need to make sure that your heap has adequate size. In the project-options dialog box choose the Asm tab. Here you can find two symbols for the C-stack and the heap, because the sizes of these memory areas are determined in the startup code in assembly. You need to increase the size of the heap to, say, 1KB, that is 1024 bytes. But, going back to the code, another important convention of OOP is that the objects get initialized before any other operations can be performed on them. Object-oriented languages call the class constructors automatically, which you will see in a couple of minutes in C++, but in C you need to do this manually, by calling the “ctor” functions explicitly. By the way, you might recognize the same pattern already used with respect to the QXThread objects from the QP/C framework. So now yo know what that is. After the initialization, you can perform any defined operations on your objects. For example, you can move them around with the moveBy() operation. Or you can check their relative distances from each other. Here I use the assertion macros from the QP/C framework to assert the basic properties of the distance operation, which are: - the distance from a shape to the same shape must be zero - the distance from shape1 to shape2 must be the same as shape2 to shape1 - and the distance between two shapes s1 and s2 must be less or equal to the sum of distances to any other shape, like ps3. At this point, you can also check that the moveBy() operation is not allowed for const Shape objects. For this you can declare a Shape const pointer ps1 pointing to the s1 shape. When you try to call the moveBy() operation on this pointer, the compiler protests with an error. This is an example of the main guiding principle for designing class interfaces: your interfaces should be easy to use correctly and hard to use incorrectly. Now, let’s have a look at how encapsulation works internally and how to debug object-oriented code based on classes. First, as you step through the main code, you can watch the call to the malloc function. The function takes the sizeof Shape parameter, which turns out to be 4 bytes. This makes sense, because the Shape struct consists of two two-byte integers. Next, you can see that a call to a class operation, such as the constructor, places the "me" pointer in the r0 register. This is per the ARM Application Procedure Call Standard (AAPCS) introduced back in lesson 9. As you step inside a class operation, you can see in the disassebly window that the access to class attributes uses the offset-from-a- register addressing mode from the "me" pointer in r0. This is very efficient. Also, inside a class operation, it is always convenient to watch the "me" pointer, which is at the top of the locals window. This means that you can very conveniently see the class attributes. As you keep stepping through the code, you can see that this pattern of placing the "me" pointer in the r0 register repeats for ALL class operations. So in the end the "me" pointer helps you not only in understanding the code, but also in debugging, because you always easily see the object you are dealing with. As it turn out this also results in excellent performance. So, this is how you can emulate class encapsulation in C. But what about "real" object-oriented programming languages? How would that work in C++, for example? Well, let me show you right now. Specifically, let’s take this whole lesson and translate it into C++. This will allow you to directly find out how it differs from C. In the first step, let’s just copy the whole lesson 29 directory and rename it lesson29_cpp. Get inside the new directory and rename all dot-C files to dot-CPP. Finally, drop the project lesson onto the uVision IDE to open it instead of the previous project in C. The IDE has obviously problems opening the no longer existing dot-C files, which you need to remove from the project. Instead, you need to now add the new existing dot-CPP files. You re-build the whole project, which produces some compilation errors. The first error is in converting the void pointer returned from malloc to the Shape pointer ps3. This was fine in C, because void pointer implicitly converts to a pointer of any other type, but C++ is stricter and requires exact match between the types. The other errors are of the exact same nature, because void pointers are used instead of pointers to the QPC event type QEvt. But after these few cosmetic changes, the project compiles and links error and warning-free. That’s very interesting, because it shows you that C++ compiler accepts the C implementation just fine, it is only a bit more strict in terms of type checking. This means that you can get into C++ programming quite easily form C and that you can treat C++ at first as a better, more strict version of C. But of course C++ offers you much more than this. It is truly an object-oriented language that supports classes directly. So, let’s now convert your Shape class emulated in C into a true C++ class. The first thing is to replace the keyword struct with class followed by the class name. You also don’t need the awkward typedef anymore, because the name after the class keyword in C++ can be used as a type by itself, without the class prefix. Also, because a C++ class encompasses both attributes and operations, the closing brace goes now after the last operation. Now, because class operations are truly inside the class, you no longer need the pre-pended class name in front. But this concept of mangling the name of the class with the name of the operation is not completely lost. As you will see later in this lesson, the C++ compiler actually also performs such name-mangling behind the scenes. The class attributes should be encapsulated, meaning that they should be inaccessible outside of the class. In C it was just a coding convention, but C++ provides the special "private" keyword to restrict the access to these class members. On the other hand, class operations need to be publicly accessible, for which C++ provides the "public" keyword. Now, regarding the constructor operation, C++ supports class constructors directly and calls them automatically whenever the class is instantiated. Because of this special status, the constructor must have a special name, which is the same as the name of the class. A C++ constructor also cannot return any value. Now, the most interesting aspect of any class operation, including the constructors, is that in C++ they all take an implicit parameter called "this" that is exactly like the "me" pointer you applied in C. Therefore, because the pointer to the class instance, now called "this", is already provided implicitly by the C++ compiler, you don’t need to duplicate it, so you need to remove the "me" pointer. The "me" pointer in the distanceFrom() operation is a bit tricky, because it has the const qualifier that you don’t want to lose. C++ provides for this the special syntax, where you move the "const" qualifier after the operation. By the way, you could have named the "me" pointer as "this" in C, and the C compiler would happily compile such code. But that original C code would NOT compile in C++, because "this" is a keyword in C++. The moral here is to avoid using keywords for your identifiers, even if they are in different, but related languages like C and C++. OK, so this is the complete Shape class declaration in C++. Now, let’s move on to adjust the definitions of the class operations in the shape.cpp file. So, here it is. Let’s skip the constructor for the time being, and focus on the regular class operations. The first thing you need to change is to replace the underscore with the colon-colon scope resolution operator, which tells the C++ compiler that the operation belongs to a given class, such as Shape in this case. Next, you need to remove the “me” pointer from the signature. Now, inside the body of the function, let’s replace the “me” pointer with the “this” pointer to show you that this implicit pointer really exists and is indeed used to access the class attributes. The same goes for the distanceFrom() operation, except you need to be careful with the const qualifier, just as you did in the declaration of this operation. In this case, let’s simply remove the “me” pointer also from the body of the function without using the “this” pointer, because the C++ compiler will recognize the names of class attributes and will implicitly add the “this” pointer in front of them. And finally, with the constructor, you can also apply the same adjustments and it will also compile like that. But the truly C++ way of initializing the class attributes in a constructor is by means of the constructor initializer list, which looks like this. Now, let’s try to compile just this one shape.cpp file. As you can see the C++ compiler accepts the Shape class without any errors or warnings. To finish off the conversion to C++, you still need to adjust the actual use of the Shape class in the main.cpp file. Here, you need to replace the explicit calls to the Shape constructor with initialization right at the points of allocation of the Shape objects. For example, the static object s1 needs to be initialized right where it is allocated. Similarly, the automatic object s2 must also be initialized at the point of allocation. A dynamic object, like ps3 here, is allocated in C++ by means of the C++ new operator, which is followed by the class constructor. While you are at it, any object allocated with new must be disposed of by means of the C++ delete operator. Now, the syntax of calling class operations is different in C++ and is performed by means of the dot-operator that in C was used only for accessing data members of a structure. Specifically, the moveBy operation is invoked by first specifying the object then the dot-operator and then the operation name and argument list. An operation on a pointer to an object is invoked by the arrow- operator, which in C was used only for accessing member of a structure, but in C++ just like the dot-operator is used for both attributes and operations. Please note that while the syntax might be different between C and C+ +, the actual information supplied for each operation is identical in both cases. After completing all the changes, you can see that the project builds cleanly. So now, comes the most interesting part, which is to see the machine code generated by the C++ compiler and to compare it with the previous code generated by the C compiler. But first, even before opening the debugger, go to the Shape class implementation and set a breakpoint in the Shape constructor. Now, launch the debugger. Interestingly, the code stops at the breakpoint in the Shape constructor, even before the main function was called. Indeed, when you examine the call stack, you can see that the Shape constructor is not called from main, but from some “cpp_initialization code”. This is an interesting observation for two reasons: First, you just learned that in C++ the constructors of static objects, like s1, are called even before the main() function. And second, you just learned that C++ requires an extra step in the startup code to invoke these static constructors. But when it comes to the actual machine code generated by this very different C++ syntax of constructor initializer list, it turns out to be actually identical to the code generated from the C implementation. When you continue from here, you can see that the Shape constructor has been called again, but this time from main. Indeed this is the constructor for the automatic s2 object inside main. When you keep stepping from here, you can see the invocation of the new operator. Keep single stepping and... as you can see, you arrive at a call to malloc. So, you just found out that the C++ operator new calls malloc() internally. Moreover, the parameter passed to malloc in r0 is 4, which means that the size allocated for the C++ Shape object is 4 bytes. This is again exactly the same as it was in C. When you continue, you can see that the Shape constructor is invoked for the third time, which means that the new operator also calls the constructor for the object allocated dynamically. So, to quickly summarize, you just saw how C++ ensures that the constructor is invoked in every case after allocating an object. OK, so now let’s set move the breakpoint from the constructor to the first invocation of the moveBy() operation. When you continue and hit the breakpoint, you can compare the C++ code for calling moveBy() with the corresponding call in C. As you can see they turn out to be... identical. After your step inside the moveBy() operation, it is a good idea to watch the locals window, where conveniently the “this” pointer shows at the top. This is exactly like the “me” pointer was in C. Moreover, the actual machine code for the moveBy() implementation in C++ turns out to be again exactly the same as the C code before. I’m not going to repeat it for the distanceFrom() operation, because I hope you are getting the idea: The C++ compiler generated identical code as your original C emulation of classes for both the invocations and implementations of the operations. So, now I hope you know how classes and encapsulation work and what’s going on behind the scenes in C++. Before I wrap up for this lesson, the last aspect of encapsulation I’d like to explain is how it relates to the RTOS, because of course you can use classes and RTOS together. Imagine, for example that you move the static object s1 to the top of the file and that you invoke the moveBy() operation from the blinky1 thread, You also copy the distanceFrom() operation on the same object s1 to your blinky2 thread. Please note that the assertion that a distance from s1 to s1 must be zero should be always true and that it is checked downstream from the semaphore-wait, which means that it will run only after you press the SW1 button on your TivaC LaunchPad board. As you can see, the project still builds cleanly, so let’s open it in the debugger. This time, before running the code, set a breakpoint in the Q_onAssert() handler, so that you immediately know should any of the assertions fail. Now, run the code free and start pressing the SW1 button. As you can see, most of the time the code keeps running, which means that the assertion in blinky2 evaluates successfully thousands of time. But keep trying, and... Oops!!! You hit an assertion. Check it, and indeed the assertion failed inside your blinky2 thread. The reason is a race condition around the shared s1 object, which is being modified in one RTOS thread – blinky1 while it is used in another RTOS thread – blinky2. If you don’t remember what race condition is, please refer to lesson 19. The important corollary from this is that even though you applied encapsulation for accessing the shared object s1, it didn’t really prevent a race condition. And after what you’ve seen today, I hope you understand why. You just saw that encapsulation works internally in C++ exactly the same way as you emulated it in C, and boils down to simple function calls. This might hide the internal implementation, but does really nothing to prevent race conditions. To achieve a true encapsulation for concurrency you need to go beyond the concept of a simple class and employ so called *active* class also known as the *active object* design pattern. This active object pattern is a fusion of concurrent programming, object-oriented programming, and event-driven programming and I will talk about active objects in the future lessons. But this is it for this lesson. Toady, you have learned about the concept of encapsulation, that is, classes and objects both in C and in C++. In the following lessons on Object-Oriented Programming you will learn about the two other fundamental concepts of inheritance and polymorphism. If you like this channel, please subscribe to stay tuned. You can also visit state-machine.com/quickstart for the class notes and project file downloads.