Up to the advancement of C++, the focus was on creating efficiencies within the standardized programming language itself. Programming languages that precede C++ drive efficiency through libraries, the runtime environment, or the IDE. Thus, a historical review of how classes evolved may be the best way to provide context.

The term analog computer refers to a subset of devices that use continuously variable mechanisms to model a problem in hopes to return a real-world solution. In an abstract sense a lever fits this description: place a rock as a fulcrum depending on the leverage needed, then programing the long end to go down will resolve a rock (the load) to go up for example. Being less frivolous with our interpretation, the Antikythera Mechanism predating the Common Era forced the modern world to rethink the perceived simplicity of the ancient societies. Other examples include: Differential Analyzers; Fire Control on Battleships; and of course, in time, the predecessor to the calculator (the modern adding machine). The commonality with all these machines is that they are analogous to a compiled application, in that they only do one thing, or a small subset of one-things. But how are they programmed? Well, a multiplier is a lever, an integrator is a ball and disk, etc.

Fast forward to a time where memory is programmable and where processors exceed the capability of mechanical computers and fixed-program computing. To a time where the “developer” can repurpose the computer without needing to change physical components. While in this time operators aren’t exactly programming in 1’s and 0’s, they are needing to use highly specific commands to directly manipulate registries and manually shift memory contents. What is being described is probably referred to as Assembly Language, or just Assembly.

The example below demonstrates the momentous task of printing to the console, something that would take one line in a modern programming language. “64-bit Linux installations use the processor’s SYSCALL instruction to jump into the portion of memory where operating system services are stored. To use SYSCALL, first put the system call number in RAX, then the arguments, if any, in RDI, RSI, RDX, R10, R8, and R9, respectively... Here it is in the NASM assembly language:” https://cs.lmu.edu/~ray/notes/x86assembly/

; ----------------------------------------------------------------------------------------
; Writes "Hello, World" to the console using only system calls. Runs on 64-bit Linux only.
; To assemble and run:
;
;     nasm -felf64 hello.asm && ld hello.o && ./a.out
; ----------------------------------------------------------------------------------------

             global    _start

            section   .text
_start:   mov       rax, 1                       ; system call for write
             mov       rdi, 1                        ; file handle 1 is stdout
             mov       rsi, message            ; address of string to output
             mov       rdx, 13                     ; number of bytes
             syscall                                    ; invoke operating system to do the write
             mov       rax, 60                     ; system call for exit
             xor       rdi, rdi                        ; exit code 0
             syscall                                    ; invoke operating system to exit

             section   .data
message:  db        "Hello, World", 10      ; note the newline at the end


It is clear from the example above that, if it weren’t for the great comments, an extensive cerebral dictionary is required to review this code. While this low-level functionality can be packaged into functions in Assembly, and therefore libraries, advances in computing capabilities enabled the evolution of a computer program that packages higher-level code into condensed machine code that almost rivals the efficiency of Assembly. This advancement is the birth of the modern compiler. Further advances in the compiler meant that the developers mind can be unleashed to make very complicated programs, such as operating systems. Over this period of incremental advancement, many new languages popped up, COBAL, FORTRAN, BASIC, to name a few. But it was the C programming language that stood the test of time and is still widely being used today (albeit in highly optimized embedded applications).

The major differentiator of the C programming language, aside from the compiler producing code as efficient as Assembly, is that it is almost human readable. Below is the same console write program written in C:

#include <stdio.h>
void main() {
   printf("Hello, World!");  // displays the string inside quotation
}


The C programming language brings many advances: the free movement of data across functions, dynamic memory management, wider availability of libraries, not being specifically targeted to any certain hardware, etc. But the advantage that applies in this context is the concept of structures. A structure in its simplest definition is a user defined data type, however a “struct” can be inside the definition of another “struct”. This simple concept would prove to have enormous power, because it marks the beginnings of hierarchical architecture.


Slide from Part 1 of video tutorials.

As shown data can be encapsulated into related blocks. Additionally, data in lower tiers of hierarchy can be automatically managed by handling the higher structures.

By extension, if we not only encapsulate the relevant data, but also the relevant functionality in the hierarchy, we have what is called a Class in C++!. This logic strategy is called Object Oriented Programing. The figure below demonstrates how a microscope’s functions and variables can be managed using an OOP approach.


Slide from Part 1 of video tutorials.

The example class below is the protoype for our message queue object:

namespace alx {
	namespace system{
		class MessageQueue {
			std::deque <char*> msgs;
		public:
			MessageQueue();
			~MessageQueue();
			void addBack_so(char* _message);
			void addBack(char* _message);
			void addBack_so(const char* _message);
			void addBack(const char* _message);
			void addFront_so(char* _message);
			void addFront(char* _message);
			void addFront_so(const char* _message);
			void addFront(const char* _message);
			bool isMessage();
			int getNext(char* _buffer, size_t _bufferLength);
		};
	}
}


Breaking it down into individual elements:

  • The namespace keyword defines a scope for the contents inside the nested brackets.
  • The class Keyword specifies the definition of a Class, with the Class name: "MessageQueue" will be defined within the braces that follow {};
  • Variables and Functions that are only to be accessed by functions belonging to the Object (an instance of the class) are also referred to as private members, shown is a std::deque object consisting of pointers to a char data type.
  • By default members are private. To expose members outside of the class, the public: keyword is used.
  • Functions with the same name as the Class, are considered constructors. The default constructor takes no arguments, but overloads are not limited.
  • Equivically, functions with the same name but preceded with a ~ symbol, are deconstructors. These are called before the object is destroyed. The deconstructor is where you would release resources such as deleting dynamic memory if needed.
  • Members that follow are similar to typical variables and functions except that they are specifically intended to be used only on the instance of the class.

The partial definition of a member function could be as follows:

namespace alx {
	namespace system {
		void MessageQueue::addBack_so(char* _message) {
			size_t _strLen = strlen(_message)+1;		        //get Length + term char
			char* _buffer = new char[_strLen];			//alocate internal buffer
			memcpy(_buffer, _message, _strLen);			//copy _message
			msgs.push_back(_buffer);					//add pointer to queue				
		}
	}
}

The only differentiator in the definition is that the function name is preceded by the class name and separated by the operator :: which is the scope resolution operator.

Then one class can be declared as a member of another class, like the following example:

#include "opencv/cv.hpp"
#include "alx_system_messageQueue.h"

namespace alx {
	namespace system {
		class csDisplay
		{
			int m_height;
			int m_width;
			cv::Mat m_background;		        //background image for screen
			cv::Mat m_data;				//data to show, full size
			cv::Mat m_rendered;			//rendered image that matches screen size
			alx::system::MessageQueue m_messages;
		public:
			csDisplay();
			~csDisplay();
			void setSize(int width, int height);
			void setBackground(char* filePath);
			void setBackground(const char* filePath);
			void setBackground(unsigned char* dataPtr, int width, int height);
			void setData(char* filePath);
			void setData(const char* filePath);
			void setData(unsigned char* dataPtr, int width, int height);
			void render();
			unsigned char* getImagePtr();
			bool isMessage();
			int getNextMessage(char* _buffer, size_t _bufferLength);
			void addMessage(const char* _message);
		};
	}
}


To see the complete example in the context of a templated architecture, see the Final Archetecture Tutorial example. Where all the pieces of a hierarchial application are put together.