CPP / C++ Notes

Table of Contents

1 Understanding C++

1.1 Features

1.1.1 C++ Benefits and Costs

Some C++ compelling features which sets it apart from most programming languages are:

Advatanges:

  • Compilation to native code - makes the code faster as it does not need to be interpreted and easier to deploye as the number of dependencies are minimized. Another advantage of native code is the obfuscation for free what makes it hard and more costly to reverse engineer.
  • High Performance and zero-cost abstractions.
  • Hardware portability and compiler availability for many processor and hardware architechtures. C++ has the widest range of processor support, so it can run on mainstream x86, desktop processors; mobile phone ARM-based CPUs; GPUs; embedded processors and microcontrollers.
  • Direct access to the operating system C-API and system calls.
  • Interoperability with C libraries.
  • Direct Access to Memory which is important on embedded systems for accessing memory-mapped IO devices.
  • Perfomance-oriented language: C++ has many features which can help get the maximum performance as possible from the hardware:
    • Native Code Compilation
    • Strong static typing
    • Compiler optimizations
    • Inline functions
    • Templates
    • Pointers
    • Lack of garbage collector
    • Custom-allocators
    • Lots of optimized standard data structures (STL containers)
    • Inline-assembly -> The inline-assembly support is compiler specific and not required by the standard. Assembly is also processor-specific and not portable.
    • Insider knowledge of compiler makers about the operating system and the processor architechture.

Disadvantages:

  • Language complexity
  • Tooling
    • Due to the C++ complexity, it is hard to parse C++ code and consequently build code assintance tools such as refactoring tools, IDEs, linters, code generators and so on. This shortcoming is being addressed by Clang/LLVM tools that provide libraries for parsing C++ code.
  • Slow compile-time
  • Compiler's cryptic error messages.
  • Lack of garbage collector. While the lack of this feature can help to achieve better performance and efficiency, it requires manual memory management which can be error prone and lead to memory leaks. However, smart pointers and the RAAI technique can help to solve this issue.
  • Lack of a comprehensive standard library.
    • The standard library still lacks lots of libraries that other programming languages take for granted such as CSV parsers, XML parsers, database drivers, graphical user interfaces and so on.
  • ABI - Application Binary Interface Issues - due the non-standardized ABI, it is almost impossible to link object-code (aka compiled code) compiled with different compilers. This is why, unlike Java and C#, there is almost no pre-compiled libraries for C++ what makes the binary reuse hard.
  • Lack of agreed package manager.
    • While there are many C++ package, there still no standard one yet. Due to ABI issues, C++ package managers only provide packages in source format andd don't provide precompiled packages as shared libraries or static libraries as Java package manager do.

1.1.2 C++ Language features

C++ Supported Paradigms:

  • Imperative / Procedural as C and Pascal
  • Object Oriented
  • Functional Programmign since C++11 with the introduction type deduction (auto keyword) and lambda functions.
  • Generic Programming - Template metaprogramming, a combination of statically typed duck-typing and lisp-like metaprogramming that allows efficient code generation at compile-time.

Other Features:

  • Automatic memory management.
  • No garbage collector.
  • Low level access to system and operating system services or API in a similar way to C.

1.1.3 C++ New Features and Standards

  • C++98 - ISO/IEC 14882:1998
    • Old C++ standard.
  • C++03 - ISO/IEC 14882:2003
  • C++11 - ISO/IEC 14882:2011
    • C++11 Working Draft http://wg21.link/n3242
    • See:
    • Features which supports Functional Programming
      • auto keyworkd for type deduction (aka type inference)
        • Which relieves the developer from writing all types and simplifies the code.
      • Lambda functions and std::function
        • Introduces the functional programming paradigm in C++ allowing users to write higher-order functions and reusable algorithms.
      • Tuples
        • allows returning multiple values
      • Move Semantics:
        • Transfer resource ownership from temporary objects. This feature removes the runtime overhead of temporary objects and also allows to return containers such as vectors, string and etc from functions without runtime overhed or deep copy.
    • Library Support
      • Smart Pointers => unique_ptr and shared_ptr
        • Provides memory and resource management making the programs less prone to memory or resource leaks. Those pointers implments the RAII (Resource Acquisition is Initialization) idiom which takes advantage of C++ deterministic destructor for releasing resources no longer needed.
      • Regex
      • Cross-platform concurrency features.
        • Example: threads, monitors and locks which works on all supported platforms and operating systems.
    • Misc
      • nullpr - type-safe null pointer
      • Ranged-based loop
    • Compile-time metaprogramming features
      • Variadic Templates
      • Contexpr
        • Allows compile-time computations such as computing random numbers, encrypting string at runtime or generating look-up tables.
      • Static assert
      • String literals
      • User-defined literals
  • C++14 - ISO/IEC 14882:2014
    • C++14 Working Draft - http://wg21.link/n4296
    • See: C++14 Language Extensions, C++ FAQ
    • Binary Literals: int x = 0b1001001 -> This feature is useful for embedded systems and dealing with bitmasks and hardware registers.
    • Digit Separators - allows to write the number pi = 3.1415927 as pi = 3.141'592'7
    • Generalized return type deduction.
    • decltype(auto)
    • Genric Lambda - Now it is possible to use the keyword auto for deducing or infering the type of lambda arguments.
    • Variable Templates
    • Extended Constexpr - contexpr functions can have loops, mutating variables and don't need to be recursive as it was before, consequently constexpr is able to peform more complex compile-time computations.
    • [ [ deprecated ] ] attribute -> enables annotating an entity as deprecated and prints a compilation warn if a deprecated entity is used.

Compiler Support:

1.2 C++ Comparison and Alternatives

1.2.1 C++ Comparison with Java and C#

General overview: Unlike most object oriented languages such as Java and C#, C++ doesn't have a class hierarchy, single inheritance, garbage collection and a comprehensive standard library. However in some situations, C++ benefits such as direct access to the operating system, memory, system calls and low level; interoperability with C libraries; and above all, high performance computing may offset the language complexity costs.

This table shows a comparison between C++ and mainstreams object oriented programming languages like Java and C#.

Table 1: Comparison C++ X Mainstream high level programming languages (C#, Java, Python …)
Feature C++ Java C# - .NET
Native Code Yes No No
JIT - Just-In-Time Compilation No - No needed, since C++ is native. Yes Yes
Virtual Machine No Yes Yes
Garbage Collection No Yes Yes
Memory Management semi-automatic auto auto
       
Object Oriented Features      
OOP - Object Oriented Yes Yes Yes
Class Hierarchy No Yes Yes
Reflection No Yes Yes
Single Inheritance No Yes Yes
Multiple Inheritance Yes No No
Properties No No Yes
Inner classes, aka nested classes Yes Yes Yes
Anonymous classes No Yes No
Operator Overloading Yes Not Yes
Marshalling      
Conditional Compilation - preprocessor Yes No Yes
Lambda functions Yes - since C++11 Yes - since Java 8 Yes
Generics Yes Yes Yes
*Template metaprogramming Yes No No
Dependency, package manager No Yes Yes
       
Standard Library Features      
Broad Cross Platform Support No - Provided by boost library. Yes No
GUI toolkit in standard library No Yes - Swing, JavaFX Yes - WinForms, WPF
Collection Library Yes - STL. Yes Yes
Sockets No - Provided Boost library. No No
Threads Yes. Since, C++11 Yes Yes
File System C++ 11 -> No, C++17 -> Yes. Yes Yes
       
       

Notes and observations:

  • Class Hierarchy => In Java, every object inherits from the root class java.lang.Object. Objects can be cased from its Class or to 'Object'. It also allows put multiple unrelated java objects in the same container. C# too has a root class System.Object from which all classes inherits:
  • Virtual Machine => The C++/CLI implementation, also known as Managed C++ runs in the .NET virtual machine and has garbarge collector. But this implementation is only supported on Windows.
  • C++ DO have package managers such as Conan, Buckaroo and Vcpkg. But they are not standard and not widely adopted. In addition, C++ package managers generally don't provide pre-compiled packages as Java and C# / .NET. Actually, due to non standard ABI - Application Binary Interface issues, they only provide source packages that are compiled locally. As far it is known, the C++ package managers also don't provide a way to install C-shared libraries dependencies locally such as GNU-scientific library, Opengl, OpenCV, Gtk, Blas-Lapack shared libraries and so on.

Main Differences between C++ and other languages

  • Value Semantics
    • Classes are trated as primitive types by default.
  • Stack or Heap Allocation
    • Objects can be allocated on the stack and on the heap.
  • No Garbage Collection
    • Although, there is no garbage collection, the memory management can be performed in semi-automatic way by using smart pointers from C++11 unique_ptr or shared_ptr. The shared_ptr can emulate garbage collectors as it uses an internal reference counter.
  • Deterministic Destructor
    • The destructor method is always called when an object goes out of scope or an exception happens.
  • Many types of constructors
    • C++ has the default constructor, copy constructor and move constructor.
  • Many types of inheritance
    • public, protected, private and virtual inheritance.
  • Template Metaprogramming / Generic Programming
    • Which allows generics and type-safe code generation with zero rutime overhead.
  • Pointers
    • Most languages doesn't provide access to pointers and calling C-libraries directly.
  • Minimal standard library
    • Lacks many things other languages have for granted such as database drivers, sockets, SSL/TSL, file system, user interfaces and so on. Note: only on C++17 that C++ has file system access derived from Boost.FileSystem the standard library.

C++ Terminology

C++ has a different terminology from mainstream programming languages which is shown in the following table.

Table 2: C++ terminology simplification
C++ Other OOP Languages Comment
  i.e: Java, Python, …  
Class Class C++ class' fields and methods are private by default.
Struct Class A C++ is the same as a Class, but fields and methods are public by default.
Class or Struct Abstract Class A C++ abstract class have at least one virtual function.
Class or Struct Interface A C++ interface is a class signature with only pure virtual functions (abstract methods.)
     
Member Function Instance methods  
Static Member Function Static method  
Virtual Functions - Only methods annoted with virtual can overriden by subclasses
Non Virtual Functions - Methods without virtual annotation cannot be overriden by subclasses.
Pure Virtual Function Abstract method (method without implementation)  
STL Containers Collection library - Lists, Maps, Tuples … C++ name its collections as containters
     

Java X C++ Collections Comparison

Java C++
java.util.ArrayList std::vector
java.util.LinkedList std::list
java.util.Stack std::stack
java.util.Queue std::queue
java.util.Deque std::deque
java.util.TreeSet std::set
java.util.HashSet std::unordered_set
java.util.TreeMap std::map
java.util.HashMap std::unordered_map
   

See:

1.2.2 Comparison C X C++(CPP)

One of the most compelling advantages of C++ is the compatibility with C programming language and the ability to interoperate with C code and C shared libraries. The interoperability with C is necessary as it is the fundamental language of system programming which most operating systems, programming languages and domain-specific shared libraries such as GSL (GNU Scientific Library) or OpengGL are implemented. In addition, most operating system APIs and services are available or exposed in C.

While previous knowledge of C is not required for learning C++, it is still necessary to know at least how to read C code in order to access it from C++ or to create high level C++ wrappers to it.

Note: C++ is not C with classes, it is complete different language, however it inherits many concepts from C and supports a subset from C and it is even possible to compile many C code with a C++ compiler.

Some applications implemented in C:

  • Linux Kernel
  • Free BSD operating system
  • Windows API - Win32
  • U-nix sockets API.
  • OpenGL
  • Gtk GUI Toolkit
  • X-Windows System
  • Python Programming Language (CPython Interpreter)
  • Ruby Programming Language
  • Lua Programming Language
Feature C++ / CPP - C Plus Plus C
Creator Bjarne Stroustrup Dennis Ritchie
Paradigms Imperative, Object Orientated, Gneric / Meta programming and Functional Procedural / Imperative
Some use cases High Performance Computing, Embedded Systems, Games, … System programming and embedded systems …
Standard ABI No Yes
Collection Library (Containers) Yes - STL (standard) and others. No
Garbage Collector No No
Memory Management Manual and Semi automatic (provided by smart pointers.) Manual
Error Handling Mechanism Function return values, global flags and exceptions. Function return values and global flags such as errno
Exceptions Yes No
Namespaces Yes No
File Extensions .cpp (c++ source code) and .hpp (c++ header file). .c (C-source code) and .h (header file)
Metaprogramming facilities Macros + Templates + Inline Functions + Contexptr Macros
String Manipulation std::string C doesn't have string types, just array of characters.
Direct Access to Memory yes - using reinterpret_cast or C-style casting. yes - using C-style casting.
Run barebones without OS yes yes - C has more compillers for embedded devices than C++.
Threads in standard library Yes - since C++11 No - It is platform dependent.
  • Direct Access to Memory:
    • Note: It means direct access to a memory location given by its address. This feature is used for accessing memory mapped IO in embedded systems and low level system programming.

File Extensions

  • Source Files
    • *.cpp - C++ source files.
    • *.hpp - C++ header files.
    • *.o - Object Code
  • Windows
    • *.exe - PE32 - Windows Executable
    • *.dll - Windows Shared Library
  • Unix (Linux, BSD …)
    • (No extension or *.bin) - ELF - Unix Executable
    • *.so - Unix Shared Library

Primitive Data Types

Type stdint.h type Size (Bytes) Size (Bits) Range Description
bool   1 8   Boolean 0 (false) or 1 (true)
char   1 8   Ascii character
unsigned char uint8_t 1 8 0 to 255 1 byte integer
signed char int8_t 1 8 -128 to 127  
           
short int16_t 2 16   16 bits signed integer
unsigned short uint16_t 2 16    
           
int int32_t 4 32   32 bits signed integer
unsigned int uint32_t 4 32   32 bits unsigned integer
           
           
float   4 32   32 bits IEEE 754 single-precision float point number
double   8 64   64 bits IEEE 754 double-precision float point Number
long double   10 80   Extended precision non-IEEE float point number
           
  • Note: The type char can be understood as an 8-bits integer.

1.3 Minimal C++ Programs

1.3.1 Simple minimal C++ program

#include <iostream>
#include <string>

int main(){
    std::cout << "Hello world user" << "\n";
    std::cout << "Enter your name: ";
    std::string name;
    std::getline(std::cin, name);
    std::cout << "Be welcome to C++ land: " << name << "\n";

    // Wait user type a single character
    std::cout << "\n" << "Type RETURN to exit";
    // Read a character from standard input (console)
    std::cin.get();

    // Status code
    return 0;
}

Note: The following code is necessary to allow inspecting the program output on Windows after the user click at it. This code prevents the program from exiting immediately by asking the user to type Return key to proceed. It is not necessary on Linux or any other U*nix-like operating system such as Mac OSX or BSD.

std::cout << "\n" << "Type RETURN to exit";
// Read a character from standard input (console)
std::cin.get();

The main() function returns 0 (zero) as status code for indicating that the program was terminated gracefully or any value other than zero indicate that program was terminated with failure.

return 0;

The main function could aso return:

  • To indicate success. Same as returning (0).
// Same as 0 
return EXIT_SUCCESS; 
  • To indicate failure. Same as returning (1)
return EXIT_FAILURE;

Compiling and running:

  • Compilation flags:
    • -o <filename>
      • => Sets the name of the executable generated by the compiler. If executable name is not set, the compiler will generate the file a.out on *Nix like systems (Linux, MacOSX, FreeBSD and etc.) or a.exe on Windows.
    • -std=c++14
      • => Enable C++14 Features. It could also be -std=c++11 (for C++11 features), -std=c++1z (for C++17 features)
    • -g
      • => Generate debug symbols
    • -Wall
      • Increase verbosity level.
  • Compiling with Clang
    • => Clang compiler provides better error diagnosing and more user friendly debug messages than other compilers.
$ clang++ minimal-program1.cpp -o minimal-program1.bin -g -std=c++14 -Wall 
  • Compiling with GCC
$ g++ minimal-program1.cpp -o minimal-program1.bin -g -std=c++14 -Wall  
  • Compiling on Windows with Mingw (GCC for Windows)
    • On Windows, the executable can be run by just clicking on it.
$ g++ minimal-program1.cpp -o minimal-program1.exe -g -std=c++14 -Wall 
// On Console (cmd.exe)
$ minimal-program1.exe 
// Or 

The compilation produces a native executable with:

  • Specific binary format required by operating system. On Linux, Android and BSD variants this format is ELF (Executable Linkable Format); on Windows, this binary format is PE32 or PE64 (Portable Executable) and on MacOSX this format is MachO.
  • Specifc machine code (Instruction set) for the processor architecture the program was compiled. The most common processor architecture used in servers and desktop computers are Intel-x86 and Amd-x86-x64. For consumer mobile devices, the most common processors are the ARM-based processors that are used in many Android-based phones, IPhone, IPad and etc.
  • Specific system-calls and library calls for the operating system the program was compiled for.
  • Additional Notes:
    • A compiled C++ program generally cannot be run in other operating because different operating systems have different binary executable formats, different systems calls and core libraries.
    • The Wine project allows running programs compiled on Windows on U-nix like operating systems, including Linux, BSD and MacOSX by translating the PE32 binary format and the Windows system-calls to the host OS system call.
    • C++ programs can also be compiled to run bare metal, it means without any operating system. This type of compilation is widely used for embedded systems, microcontrollers, firmwares, BIOs, operating systems and so on.

Running:

$ ./minimal-program1.bin 
Hello world user
Enter your name: Somebody else
Be welcome to C++ land: Somebody else

Type RETURN to exit

1.3.2 Variations of main function

  1. Overview

    The main function can have the following variations:

    • Simple main function.
    int main();
    
    • Main function with command line arguments
    int main(int argc, char **argv);
    
    • Main function with command line arguments and environment variables.
    int main(int argc, char **argv, char **environ);
    
  2. Example: Minimal program with command line arguments

    Exmaple: Command line arguments.

    #include <iostream>
    #include <string>
    
    int main(int argc, char **argv, char **environ){
        if(argc < 2){
           std::cerr << "Error: invalid command line argument. Expected -args or -env "
                     << "\n";
           // Exit  immediately (return 1)
           return EXIT_FAILURE; 
        }
    
        if(std::string(argv[1]) == "-args"){
           std::cout << "Number of command line arguments = " << argc << "\n";
           std::cout << "argv[0] = " << argv[0] << "\n";
           std::cout << "\n";
           std::cout << "Showing command line arguments:" << "\n";
           std::cout << "+-----------------------------+" << "\n";
           for(int i = 0; i < argc; i++)
                   std::cout << "argv[" << i << "] = " << argv[i] << "\n";      
           return EXIT_SUCCESS;
        }
    
        if(std::string(argv[1]) == "-env"){
           std::cout << "Show environment variables" << "\n";
           char** env = environ;
           while(*(env++) != nullptr)
                   std::cout << " => env = " << *env << "\n";
           return EXIT_SUCCESS;
    
        }
        std::cerr << "Error: invalid argument <" <<  argv[1] << "> " <<"\n";     
        // return 1
        return EXIT_FAILURE;
    };
    

    Compiling:

    $ clang++ minimal-program2.cpp -o minimal-program2.bin -g -std=c++1z -Wall
    

    Running:

    $ ./minimal-program2.bin 
    Error: invalid command line argument. Expected -args or -env 
    
    $ ./minimal-program2.bin -args opt1 opt2 key=value
    Number of command line arguments = 5
    argv[0] = ./minimal-program2.bin
    
    Showing command line arguments:
    +-----------------------------+
    argv[0] = ./minimal-program2.bin
    argv[1] = -args
    argv[2] = opt1
    argv[3] = opt2
    argv[4] = key=value
    
    $ ./minimal-program2.bin sadsad
    Error: invalid argument <sadsad> 
    
    $ ./minimal-program2.bin -env
    Show environment variables
     => env = LD_LIBRARY_PATH=:/home/archbox/opt/root/lib:/home/archbox/opt...
     => env = XDG_MENU_PREFIX=gnome-
     => env = MODULES_RUN_QUARANTINE=LD_LIBRARY_PATH
     => env = LANG=en_CA.UTF-8
     => env = GDM_LANG=en_CA.UTF-8
     => env = HISTCONTROL=ignoredups
     => env = DISPLAY=:0
     => env = HOSTNAME=localhost.localdomain
     => env = OLDPWD=/home/archbox/Documents/projects/learn.cpp
     => env = GNOME_SHELL_SESSION_MODE=classic
     ... ... ... 
     => env = MODULES_CMD=/usr/share/Modules/libexec/modulecmd.tcl
     => env = USER=archbox
     => env = ENV=/usr/share/Modules/init/profile.sh
     => env = PAGER=cat
     => env = DESKTOP_SESSION=gnome-classic
     ... ... ... 
    

1.4 Simple Variable Definition and Initialization

Simple variable definition:

>> int i;
>> int ix = 200;

>> i
(int) 0

>> ix
(int) 200
>> 

>> const char * s1  = " refresh memory about C++" ;
>> s1
(const char *) " refresh memory about C++"

>> char s2 []  = " refresh memory about C++" ;
>> s2
(char [26]) " refresh memory about C++"
>> 

Multiple variable default initialization in same line:

double x, y, z;

>> x
(double) 0.0000000
>> y
(double) 0.0000000
>> z
(double) 0.0000000

Multiple variables initialization at same line:

double xx = 3.1515, yy = 3 * xx + 3.0, zz = xx * 2 + yy;

>> xx
(double) 3.1515000
>> yy
(double) 12.454500
>> zz
(double) 18.757500
>> 

double xa{3.1515}, ya{3 * xa + 3.0}, za{xa * 2 + ya};

>> xa
(double) 3.1515000
>> ya
(double) 12.454500
>> za
(double) 18.757500
>> 

Multiple initialization with assignment:

double x, y, z;
>> x = y = z = 3.0;
>> x
(double) 3.0000000
>> y
(double) 3.0000000
>> z
(double) 3.0000000
>> 

std::string s1, s2, s3;

>> s1 = s2 = s3 = "remember c++";
>> s1
(std::string &) "remember c++"
>> s2
(std::string &) "remember c++"
>> s2
(std::string &) "remember c++"
>> 

1.5 Literals and C++14 Literals

Literal Description Example Since
Integer 12905134  
Integer separated by single quote 1'290'5134 C++14
     
Unsigned integer 10 12905134U  
Unsigned integer separated by single quote 1'290'5134U C++14
     
Integer in hexadecimal base (base 16) 0xFF4A5A  
Integer in hexadecimal base separate by single quote (') 0xFF'4A'5A C++14
     
Integer in binary (base 2) format (0xF1 = 241) 0b11110001 C++14
Integer in binary format separated by single quote 0b1111'0001 C++14
     
Float point double precision (double) 15'55'100.91'51'61 C++14
Float point double precision (double) 1555'100.915161 C++14
     
Character literal 'X'  
Non printable character literal (LF Line Feed 0x0D) '\x0D'  
     

Literal Formats added in C++14

  • Binary Literals => More convenient for low level, device drivers, embedded systems and so on.
    • 0b11110001 = 0xF1 = 241
  • Numeric Literals separated by single quote:
    • 1000'000,000 => 1 billion
    • 0b1111'0001 = 0b11110001
    • 0xF'FAA'BBAC

Examples:

  • Character (char)
// Character literal
char ch1 = 'X';

// Non printable character literal - hexadecimal value 0xD which is the
// same as new line character  Unix LF Line Feed
char ch2 = '\x0D';
  • Signed Integer (trivial)
int n = 200;
  • Integer hexadecimal literal (0xFF = 255 decimal)
int x = 0xFF;
  • Unsigned Integer Literals (suffix U) - always positive literals.
unsigned int ux1 = 1000U;
unsigned int ux2 = 0xFFABU;
  • Unsigned long literals (suffix UL) - always positive literals.
unsigned long ux3 = 0xFFABUL;
auto ux4 = 0xFFABUL;
assert(typeid (decltype(ux4)) == typeid(unsigned long) && "Types should be equal");
  • Integer separated by single quote:
// Since C++14
uint32_t register_config = 0xF'AAB'987A;

// Binary literal (Number in base 2) - Since C++14
// 0b10001001 = 0x89 = 8 * 16 + 9 * 16^0 = 137
uint8_t gpioA_config = 0b10001001;
std::cout << " gpioA_config = " << static_cast<int>(gpioA_config) << std::endl;

// Binary literal with single quote separator
int gpioB_config = 0b1000'1001;
std::cout << " gpioB_config = " << static_cast<int>(gpioB_config) << std::endl;

// Number literal with single quote separator (')
long quantity = 100'000'000;
  • IEEE754 Single Precision Literals - 32 bits (suffix .f)
float flt32A = 100.23f;

float flt32B = 100.23E5f; // 100.23 * 10^5

float flt32C = .001114465111f; 

float flt32D = 0.001114465111E6f; 

// Separated by single quote (C++14)
float flt32E = 1555'100.9151f;

// Separated by single quote (C++14)
float flt32F = 15'55'100.91'51'61f;
  • IEEE754 Double Precision Literals - 64 bits (default float point literal)
double flt64A = -1120.4;

double flt64A = -20.4E3;

// Separated by single quote 
// Separated by single quote (C++14)
double flt32F = 15'55'100.91'51'61;
  • String Literal
const char* str1 = "Hello world C++";
const char* str2 = "Hello \n \xFD \xF7 world C++";

/* Represents: C:\\User\Dummy\Path  */
const char* windows_path = "C:\\\\User\\Dummy\\Path";
  • Raw String Literal R"( … )"
    • Supports multiline and characters that are used for escaping sequence such as backward slash (\) that needs to be escaped and written as (\\).
   const char* windows_path = R"(C:\Uses\Dummy\Path)";

   const char* regex = R"(^\d+\w+)";

   const char* script_source = R"(
    for(i = 0 to 10) do {
      println(i * 3)
    }

    list_files(C:\Users\data\path)
)";
  • Multi-Line Strings and literal string concatenation
    • Note: It is also can be used in C language.
// Single line string 
const char* str1 =
        " => C++98 programming language ; "
        " => Rust programming language ; "
        " => Forth programming language ; "
        " => ADA programming language ; "
        ;

std::cout << str1 << "\n\n";

// Multi line string 
const char* str2 =
        "  => Product Name: Super CFD (Computational Fluid Dynamic) Simulator \n"
        "  => Aerospace SBC - Single Board Computer \n"
        "  => State Space Model Matrix \n"
        "  => Low Pass Filter \n"
        "  => SOC - System-On-Chip \n";
std::cout << str2 << "\n\n";

Output:

=> C++98 programming language ;  => Rust programming language ;  => Forth programming language ;  => ADA programming language ; 

 => Product Name: Super CFD (Computational Fluid Dynamic) Simulator 
 => Aerospace SBC - Single Board Computer 
 => State Space Model Matrix 
 => Low Pass Filter 
 => SOC - System-On-Chip 

1.6 Control Structures

1.6.1 ternary operator - question mark (?)

Unlike the if-else statement, the ternary operator (?) or question mark operator, can solve return a value, although it cannot have complex statements like if-else. In functional languages like Lisp, Scala or Haskell, the if-else operator always is evaluated and returns

Limitation: The ternary operator cannot have complex statements like if-else.

Syntax:

{VARIABLE} = {CONDITION} ? {RETURN-IF-TRUE} : {RETURN-IF-FASLE};

Example:

>> true ? "true" : "false"
(const char *) "true"
>> false ? "true" : "false"
(const char *) "false"

>> int x;
>> x = true ? 10 : 25
(int) 10
>> x
(int) 10

>> x = false ? 10 : 25
(int) 25
>>
>>

>> std::string s;
>> x = 10;
>> s = (x < 15) ? "true" : "false"
"true"

>> x = 100;
>> s = (x < 15) ? "true" : "false"
"false"
>>

1.6.2 if-else

  1. True and false

    In C++, everything equal to zero is false and everything other than zero is true.

    • Booleans
    >> bool flag;
    
    // Size - 1 byte
    >> sizeof(bool)
    (unsigned long) 1
    
    >> flag = false;
    // Booleans are actually numbers
    >> std::cout << "flag = " << flag << "\n";
    flag = 0
    
    >> std::cout << "flag = " << std::boolalpha << flag << "\n";
    flag = false
    >>
    
    >> flag = true;
    
    // True
    >> std::cout << "flag = " << static_cast<int>(flag) << "\n";
    flag = 1
    >>
    
    
    • Booleans - If-else
    >> bool flag;
    
    >> flag = false;
    >> if(flag) { std::puts(" => True"); } else { std::puts(" => False"); }
     => False
    
    >> flag  = true;
    >> if(flag) { std::puts(" => True"); } else { std::puts(" => False"); }
     => True
    >>
    
    • Char
    char x;
    
    >> x = '\0'; // Null character
    >>  if(x) { std::puts(" => True"); } else { std::puts(" => False"); }
     => False
    
    >> x = 0x00; // Ascii code of null character
    >>  if(x) { std::puts(" => True"); } else { std::puts(" => False"); }
     => False
    
    >> x = 'a'
    (char) 'a'
    >>  if(x) { std::puts(" => True"); } else { std::puts(" => False"); }
     => True
    >> 
    
    • Integers:
    int n = 10;
    
    >> if(n) { std::puts(" => True"); } else { std::puts(" => False"); }
     => True
    >>
    
    >> n = 0;
    >> if(n) { std::puts(" => True"); } else { std::puts(" => False"); }
     => False
    >>
    
    • Double:
      • Note: Float pointers should never be tested by comparison.
    >> double num;
    
    >> num = 12.3;
    >> if(num) { std::puts(" => True"); } else { std::puts(" => False"); }
     => True
    
    >> num = 0.0;
    >> if(num) { std::puts(" => True"); } else { std::puts(" => False"); }
     => False
    
    • Pointers:
    >> double* ptr;
    
    >> ptr = nullptr;
    >> if(ptr) { std::puts(" => True"); } else { std::puts(" => False"); }
     => False
    
    >> double m;
    >> ptr = &m
    (double *) 0x7f14ccfce020
    
    >> if(ptr) { std::puts(" => True"); } else { std::puts(" => False"); }
     => True
    >>
    
    >> if(nullptr) { std::puts(" => True"); } else { std::puts(" => False"); }
     => False
    
    >> if(NULL) { std::puts(" => True"); } else { std::puts(" => False"); }
     => False
    
  2. If-else statement with assignment

    IF-else statement with assignment

    This type of assignment within the if predicate is still widely used in C.

    // Note: nullptr should not be used instead of NULL.
    FILE* fp = NULL; 
    
    if( (fp  = fopen("/tmp/file2.dat", "w")) != NULL ) {
        fprintf(fp, "File content 2");
        fclose(fp);
    }
    

    The previous code is equivalent to:

    FILE* fp = fopen("/tmp/file1.dat", "w");
    
    if(fp != NULL) {
        fprintf(fp, "File content 1");
        fclose(fp);
    }
    

    See also:

  3. IF-else statement with initializers (C++17)

    C++17 Allows defining variables in IF-else statements which is useful for writing more concise code.

    Example 1:

    if(FILE* fp = fopen("/tmp/file4.dat", "w"); fp != nullptr) {
        fprintf(fp, "File content 2");
        fclose(fp);
     }
    

    Example 2:

    if(auto fp = fopen("/tmp/file4.dat", "w"); fp != nullptr) {
        fprintf(fp, "File content 2");
        fclose(fp);
    }
    
     // Could alos be written as 
    if(auto fp = fopen("/tmp/file4.dat", "w")
        ; fp != nullptr) 
     {
        fprintf(fp, "File content 2");
        fclose(fp);
     }
    

    Example 3:

    std::vector<int> xs {1, 20, 50, 77, 100,  25};
    
    if(auto it = std::find_if( xs.begin(), xs.end(), [](int x){ return x % 11 == 0; })
            ; it != xs.end() )
    {   // This branch is executed if the expression "it != xs.end()" evaluates to true. 
        std::cout << " Found element it = " << *it << std::endl;
    }
    
    /* Output: 
       Found element it = 77
       [INFO] Finished OK.
    */
    

    Example 4:

    if( std::vector<int> xs {1, 20, 50, 77, 100,  25}
       ; auto it = std::find_if( xs.begin(), xs.end(), [](int x){ return x % 11 == 0; })
       ; it != xs.end() )
    {
        std::cout << " Found element it = " << *it << std::endl;
    }
    
    /* Output: 
       Found element it = 77
       [INFO] Finished OK.
    */
    

    See:

  4. Returning value from if-else statements (C++11)

    The C++ if-else statement cannot return a value or be evaluated to a value as the if-else statement in Lisp, Scheme, Scala, Haskell and other functional languages. Despite that the ternary operator (?) can be evaluated to some value, it cannot contain complex expressions as if-else statements.

    if-else in Scala

    // ========>>> EXPERIMENT 1 ==============//
    var x: Double = 0.0;
    var n = 10;
    
    scala> x = if(n > 100) 256.0 else 300.0
    x: Double = 300.0
    
    scala> n = 200
    n: Int = 200
    
    scala> x = if(n > 100) 256.0 else 300.0
    x: Double = 256.0
    
    
    // ========>>> EXPERIMENT 2 ==============//
    
    n = 50
    scala> x =  400 + (if(n > 100) { println("Return 256.0"); 256  } else { println("Return 300.0"); 300.0})
    Return 300.0
    x: Double = 700.0
    
    scala> x =  400 + (if(n > 100) { println("Return 256.0"); 256  } else { println("Return 300.0"); 300.0})
    Return 256.0
    x: Double = 656.0
    
    

    if-else in C++

    Desired functionality, however the following code doesn't work.

    >> double x;
    >> int n;
    >> 
    >> n = 10;
    
    // Compilation error! It doesn't work in C++ 
    >> x = if(n > 100) { 
             std::puts("Return 256");  
             return 256.0;} 
          else { 
             std::puts("Return 300.0"); 
             return 300.0; 
          }
    
    ROOT_prompt_4:1:5: error: expected expression
    x = if(n > 100) {std::puts("Return 256");  return 256.0;} else { std::pu...
        ^
    >> 
    

    It could also be as shown in the following code, however, it requires modifying the variable x in more than one place.

    double x;
    int n; 
    
    n = 200;
    
    if(n > 100){
      std::puts("Return 256");
      x = 256.0;
    } else {
      std::puts("Return 300");
      x = 300.0 
    }
    

    Solution: Wrap the if-else statement into a self-executing lambda expression.

    T result;
    
    result = [&](){
                  if(CONDITION){
                     <COMPTATIONS...FALSE>;
                     return VALUE-TRUE;
                  } else {
                     <COMPTATIONS...ELSE>;
                     return VALUE-FALSE;
                  }          
              }();
    

    An alternative way is to wrap the if-else statement into a function. The advantage of this solution is that the computation can be reused in multiple different places in the code.

    T1 input1;
    T2 input2;
    
    // The computation is wrapped into a lambda function (thunk)
    // ,depends on inputs input1, input2, ... as inputs.
    auto computation = [&](){
                   if(CONDITION(input1, input2 ...)){
                       <COMPTATIONS...FALSE>;
                       return VALUE-TRUE;
                    } else {
                       <COMPTATIONS...ELSE>;
                        return VALUE-FALSE;
                    }            
               };
    
    input1 = value1A;
    input2 = value1B;
    Result result1 = computation();
    
    input1 = value1B;
    input2 = value1B;
    Result result2 = computation();
    

    Example:

    double x;
    int n;
    
    n = 50;
    
    >> x = [&](){ if(n > 100)  {
                   std::puts(" [LOG] Return 256");
                   return 256.0;
            } else { 
                   std::puts(" [LOG] Return 300.0");
                   return 300.0;
            }}();
     [LOG] Return 300.0
    
    >> x
    (double) 300.00000
    >> 
    
    n = 400;
    
    >> x = [&](){ if(n > 100)  {
                    std::puts(" [LOG] Return 256");
                    return 256.0;
            } else { 
                    std::puts(" [LOG] Return 300.0");
                    return 300.0;
            }}();
     [LOG] Return 256
    
    

    Further Reading:

1.6.3 switch case

Swich case statements can only be used with numbers, characters or Enums. They cannot be used for processing strings or classes.

Example 1: Print month based on its number.

void printMonth1(int n){
    const char nl = '\n';
     switch(n){
        case 1:  std::cout << "JAN" << nl; break;
        case 2:  std::cout << "FEB" << nl; break;
        case 3:  std::cout << "MAR" << nl; break;
        case 4:  std::cout << "APR" << nl; break;           
        case 5:  std::cout << "MAY" << nl; break;
        case 6:  std::cout << "JUN" << nl; break;
        case 7:  std::cout << "JUL" << nl; break;
        case 8:  std::cout << "AUG" << nl; break;
        case 9:  std::cout << "SEP" << nl; break;
        case 10: std::cout << "OCT" << nl; break;
        case 11: std::cout << "NOV" << nl; break;
        case 12: std::cout << "DEC" << nl; break;
        default: std::cout << "Error: invalid month." << nl; break;
     }
}

>> printMonth1(1)
JAN
>> printMonth1(2)
FEB
>> printMonth1(12)
DEC
>> printMonth1(100)
Error: invalid month.
>> printMonth1(-1)
Error: invalid month.
>> 

or

void printMonth2(int n){
    const char nl = '\n';
     switch(n){
        case 1:  std::cout << "JAN" << nl; break;
        case 2:  std::cout << "FEB" << nl; break;
        case 3:  std::cout << "MAR" << nl; break;
        case 4:  std::cout << "APR" << nl; break;           
        case 5:  std::cout << "MAY" << nl; break;
        case 6:  std::cout << "JUN" << nl; break;
        case 7:  std::cout << "JUL" << nl; break;
        case 8:  std::cout << "AUG" << nl; break;
        case 9:  std::cout << "SEP" << nl; break;
        case 10: std::cout << "OCT" << nl; break;
        case 11: std::cout << "NOV" << nl; break;
        case 12: std::cout << "DEC" << nl; break;
     }
     std::cout << "Error: invalid month." << nl; break;
}

Example 2 : Return month name as a string based on month number.

#include <std::string>
std::string getMonth(int n){
     switch(n){
         case 1:  return "JAN";
         case 2:  return "FEB";
         case 3:  return "MAR";
         case 4:  return "APR";
         case 5:  return "MAY";
         case 6:  return "JUN";
         case 7:  return "JUL";
         case 8:  return "AUG";
         case 9:  return "SEP";
         case 10: return "OCT";
         case 11: return "NOV";
         case 12: return "DEC";
         default:
                 throw std::domain_error("Error: invalid month");
     }
}

Test code:

std::string month;    
month = getMonth(1);
std::cout << "Month = " << month << "\n";     
month = getMonth(4);
std::cout << "Month = " << month << "\n";    

try{
    month = getMonth(100);
    std::cout << "Month = " << month << "\n";    
} catch(const std::domain_error& ex){
    std::cout << "An error has happened = " << ex.what() << "\n";
}

Output:

Month = JAN
Month = APR
An error has happened = Error: invalid month

Example 3: Switch-case for string input.

As switch-case control structure cannot be used with std::string input, the best approach is to use if-else statements, if-statements with early return or hash tables.

  • Solution 1: If statements with early return.
#include <string>

int getDayOfWeekNum(const std::string& weekDay){
    if(weekDay == "Sun") return 1;
    if(weekDay == "Mon") return 2;
    if(weekDay == "Tue") return 3;
    if(weekDay == "Wed") return 4;
    if(weekDay == "Thu") return 5;
    if(weekDay == "Fri") return 6;
    if(weekDay == "Sat") return 7;
    return -1;
}

>> getDayOfWeekNum("Sun")
(int) 1
>> getDayOfWeekNum("Tue")
(int) 3
>> getDayOfWeekNum("Fri")
(int) 6
>> getDayOfWeekNum("")
(int) -1
>> getDayOfWeekNum("Error")
(int) -1
>> 
  • Solution 2: A hash table or dictionary.

Note: Static variables inside functions are initialized only once and retains their current state between function calls. They are similar to global variables, but static variables are only visible inside the function they are defined.

#include <string>
#include <map>

int getDayOfWeekNum2(const std::string& weekDay){
    static auto week_table = std::map<std::string, int> {
       {"Sun", 1}, {"Mon", 2}, {"Tue", 3}, {"Wed", 4},
       {"Thu", 5}, {"Fri", 6}, {"Sat", 7}
    };
    if(week_table.find(weekDay) != week_table.end())
       return week_table[weekDay];  
    return -1;
}

>> getDayOfWeekNum2("Sun")
(int) 1
>> getDayOfWeekNum2("Mon")
(int) 2
>> getDayOfWeekNum2("Monasd")
(int) -1
>> 

1.6.4 for loop

  • Basic for-loop
#include <iostream>

>> for(int x = 0; x < 5; x++) {
       std::cout << " x = " << x << "\n";
   }
 x = 0
 x = 1
 x = 2
 x = 3
 x = 4

>> for(double x = -10.0; x <= 10.0; x += 2.5) {
        std::cout << " x = " << x << "\n";
   }
 x = -10
 x = -7.5
 x = -5
 x = -2.5
 x = 0
 x = 2.5
 x = 5
 x = 7.5
 x = 10
>>

  • Infinity loop
>> for(;;){ std::puts(" It is an infinity loop"); }
 It is an infinity loop
 It is an infinity loop
 It is an infinity loop
 It is an infinity loop
 .,.. ... ... ...

1.6.5 while loop

#include <iostream>
#include <cstdio>

>> n = 0
(int) 0

>> while(n < 5){  std::cout << "n = " << n++ << "\n"; }
n = 0
n = 1
n = 2
n = 3
n = 4

>> int j = 0;
>> while(j++ < 5){  std::cout << "j = " << j << "\n"; }
j = 1
j = 2
j = 3
j = 4
j = 5
>> j = 0
(int) 0

>> while(++j < 5){  std::cout << "j = " << j << "\n"; }
j = 1
j = 2
j = 3
j = 4
>>

Infinity lop:

>> while(true){ std::cout << " It is an infinity loop" << "\n";}
It is an infinity loop
It is an infinity loop
It is an infinity loop
... ... ... ..

1.6.6 do - while loop

int z = -10;

// Single line
>> do{ std::cout << " z = " << z << "\n"; z++; } while(z < 0)
 z = -10
 z = -9
 z = -8
 z = -7
 z = -6
 z = -5
 z = -4
 z = -3
 z = -2
 z = -1
>>

// Multiple Lines
do{
    std::cout << " z = " << z << "\n";
    z++;
} while(z < 0)

1.7 Basic Console IO

1.7.1 Overview

C++ input/output system is object oriented which means that it is possible to write input-agnostic or output-agnostic code which can be reused without any rewritten for reading data from console, file, string and other end-points.

C++ Has the following global objects for performing IO.

  • std::cout
    • (stdout) Standard output stream used for displaying program output.
  • std::cerr
    • (stderr) Stadnard error output stream used for displaying program error and logging output. It prints by default to the console. The output of the stream std::cerr (stderr) can be separated from std::cout (stdout) output by redirecting the stederr output to a log file or discarding it.
  • std::clog
    • (stderr) => Standard logging output stream.
  • std::cin => Basic Input stream or stdin. Used for reading data from console.

Operators:

Name Operator Parent class Derived Classes
Insertion Operator << std::ostream / Output std::cout, std::cerr, std::ofstream
Extraction Operator >> std::istream / Input std::cin, std::ifstream, std::sstringstream

1.7.2 Example - Old C-IO functions

Sometimes the old C-IO functions such as printf, puts and perror may be convenient due to its simplicity and less verbosity.

Functions puts, perror

// C++ header equivalent to C <stdio.h>
#include <cstdio>
#include <string>

// Print to STDOUT (cout)
>> std::puts(" => System programming.");
 => System programming.

// Print to STDERR (cerr)
>> std::perror(" => Print error and logging to stderr.");
 => Print error and logging to stderr.: No such file or directory


// Write to file (disk file, or pseudo-files such as stderr or stdout)
// U-nix where C comes from see everything as files. 

>> std::fputs("Hello world\n", stdout);
Hello world
>> std::fputs("Hello world\n", stderr);
Hello world
>> 

Functions puts, perror and fputs with C++ strings

>> std::string text;
>> text = "Hello world C-IO facilities\n";

// Failure => Cannot print C++ string, only const char 
>> std::puts(text);
ROOT_prompt_5:1:11: error: no viable conversion from 'std::string' ...

>> std::puts(text.c_str());
Hello world C-IO facilities

>> std::perror(text.c_str());
Hello world C-IO facilities

Old C-printf and fprintf

#include <cstdio>
#include <cmath>

>> std::printf("The value of pi is equal to: %.4f\n", M_PI);
The value of pi is equal to: 3.1416

>> std::printf("The value of pi is equal to: %.5f\n", M_PI);
The value of pi is equal to: 3.14159
>> 
>> std::fprintf(stdout, "The value of pi is equal to: %.5f\n", M_PI);
The value of pi is equal to: 3.14159
>> 
>> std::fprintf(stderr, "The value of pi is equal to: %.5f\n", M_PI);
The value of pi is equal to: 3.14159
>> 

std:string userName = "Julius Caesar";
int points = 200;

>> std::printf(" => User = '%s' ; Points = %d\n", userName.c_str(), points);
 => User = 'Julius Caesar' ; Points = 200

>> std::fprintf(stdout, " => User = '%s' ; Points = %d\n", userName.c_str(), points) 
 => User = 'Julius Caesar' ; Points = 200

>> std::fprintf(stderr, " => User = '%s' ; Points = %d\n", userName.c_str(), points) 
 => User = 'Julius Caesar' ; Points = 200
>> 

Printf is not type safe:

std:string userName = "Julius Caesar";
int points = 200;

>> std::printf("User = '%s' ; Points = %d\n", points, userName.c_str());
*** Break *** segmentation violation

// Expected "c-string" (const char*) but provided int
>> std::printf("%s\n", 100);
*** Break *** segmentation violation

1.7.3 Example - Reading from Console

Reading a single variable from console (stdin):

#include <iostream> 
#include <string> 

>> double x;
>> std::cin >> x;
342.34
>> x
(double) 342.34000
>> 
>> std::cout << "Enter x: "; std::cin >> x; std::cout << " => x = " << x << "\n";
Enter x: 100
 => x = 100
>> 

>> std::string user;
>> std::cout << "Name: "; std::cin >> user; std::cout << " => user is = " << user <<Name: Julius Caesar
 => user is = Julius
>> 

Read line as string

The statement std::cin >> stringVariable can only read a single word and will ignore remaining words separated by space. In order to read the entire line until the new line delimiter is found, it is necessary to used the C++ function std::getline.

>> std::string some_line;

// User types something and then hit RETURN when he is done.
>> std::cin >> some_line;
some message to compute     

// The remaining words were ignored. 
>> some_line
(std::string &) "some"
>> 

// User types something and then hit RETURN when he is done.
>> std::getline(std::cin, some_line); 
 C++ is an old cool programming language.

>> some_line
(std::string &) " C++ is an old cool programming language."
>> 

>> std::cout << " COMMAND => " ; std::getline(std::cin, some_line);
 COMMAND => go to position 200 300

>> some_line
(std::string &) "go to position 200 300"
>> 

Read multiple lines until user sends EOF

This code reads multiple lines from console untile the user sends the EOF (End Of Signal) by typing Ctrl + D on U-nix (OSX, Linux, BSD) or Ctrl + Z on Windows.

  • Read multiple lines example 1.
std::string line;

// Read lines until user types something invalid or sends EOF 
// Ctlr + Z on Windows, Ctrl + D on U-nix (Linux, BSD, OSX ...)
while(std::getline(std::cin, line)){ 
  std::cout << "You typed = " << line << "\n";
}

 Caesar cypher
You typed =  Caesar cypher
 Hardening encryption 
You typed =  Hardening encryption
 Digital Signal Processors
You typed =  Digital Signal Processors
  ASICS and FPGAs custom hardware
You typed =   ASICS and FPGAs custom hardware
>> 
>> 

  • Read multiple lines example 2.
void runREPL(){
  std::string line;
  if(std::cin.fail()){
    std::cerr << " [INFO] Input stream failure " << "\n";
    std::cin.clear();
    std::cin.ignore();
  }
  while((std::cout << " CMD=> " << std::flush), std::getline(std::cin, line)){
    std::cout << " + Typed command was = " << line << "\n";
  }
  std::cout << "\n" << " === Finish REPL session. OK ===" << "\n";
  std::cin.clear();
}

Running:

>> runREPL()
 CMD=> Testing C++ on ROOT REPL.
 + Typed command was = Testing C++ on ROOT REPL.
 CMD=> Clang is the best compiler out there
 + Typed command was = Clang is the best compiler out there
 CMD=> Clang has the best and less cryptic error messages
 + Typed command was = Clang has the best and less cryptic error messages
 CMD=>   
 === Finish REPL session. OK ===
>> 

Read multiple variables from console (stdin):

#include <iostream> 
#include <string> 

int         id, quantity;
double      price;
std::string name;

// Read from console
>> std::cin >> id >> name >> quantity >> price ;
2351 Cooking-oil 20 3.12

>> id
(int) 2351
>> quantity
(int) 20
>> name
(std::string &) "Cooking-oil"
>> price
(double) 3.1200000
>> 

Reading structured data from console:

#include <iostream>
#include <string>
#include <deque>

struct Product{
    unsigned long id;
    std::string   name;     
    double        price;
    unsigned int  quantity;
    Product(){}
};

// Or it could also be:
class Product{
public:
    unsigned long id;
    std::string   name;     
    double        price;
    unsigned int  quantity;
    Product(){}
};

Product readProductFromConsole(){
    Product p;
    std::cin >> p.id >> p.name >> p.price >> p.quantity;
    return p;
};

// Type Alias   
using Inventory = std::deque<Product>;

Inventory readInventory(){
   Inventory inv;
   std::cin.clear();
   std::cout << "Enter product ID, product Name, product Price, product Quantity." << "\n";
   std::cout << "Type CTRL+D on U-nix or CTRL+Z on Windows when you are done." << "\n";     
   while(std::cin.good() && !std::cin.eof()){
       std::cout << " => ";
       inv.push_back(readProductFromConsole());
   }
   return inv;
}                                                                          
  • Reading a single object / record:
// User types "2000 Sugar 1.45 9000" and then types RETURN when he is done.
//------------------------------------------
>> auto x = readProductFromConsole()
2000 Sugar 1.45 9000
(Product &) @0x7f16eb8fc010

>> x.id
(unsigned long) 2000

>> x.name
(std::string &) "Sugar"

>> x.price
(double) 1.4500000

>> x.quantity
(unsigned int) 9000
  • Reading multiple data:
>> auto inventory = readInventory()
Enter product ID, product Name, product Price, product Quantity.
Type CTRL+D on U-nix or CTRL+Z on Windows when you are done.
 => 803 Cooking-oil 10.23 200
 => 2001 Sugar 2.40 500
 => 9134 Car-battery 50.0 6000
 => 8023 Milk 4.50 300 
  • Print inventory value:
 // Print inventory data: 
 for(const auto& prod: inventory){
     double value = prod.quantity * prod.price;
     std::cout << "Id       = " << prod.id       << "\n"
               << "Name     = " << prod.name     << "\n"
               << "Quantity = " << prod.quantity << "\n"        
               << "Price    = " << prod.price    << "\n"
               << "Value    = " << value         << "\n"
               << "----------------------------" << "\n";
 }

 //--------- Output -------------
 Id       = 803
 Name     = Cooking-oil
 Quantity = 200
 Price    = 10.23
 Value    = 2046
 ----------------------------
 Id       = 2001
 Name     = Sugar
 Quantity = 500
 Price    = 2.4
 Value    = 1200
 ----------------------------
... ... .... 

1.7.4 Basic IO program

Example

File: file:src/basic-io.cpp

#include <iostream>
#include <string>
#include <iomanip>
#include <vector>

int main(){ 
    // Everything is initialized as zero
    double price, sum, min, max;
    std::vector<double> dataset;
    sum = 0.0;
    min = 1e10;
    max = -1e10;        

    // Print float point numbers with 2 decimal places and fixed notation 
    std::cout << std::setprecision(2) << std::fixed;
    std::cout << "Enter the prices and type CTRL+D you are done." << "\n";

    // Execute this code block while there is any error on the stream.
    // It means, the user did not type any bad input or
    //  CTRL + D (Send EOF End of File - signal)
    int n = 0;
    while(std::cin.good()){
       // (i++) Post increment i, increment variable i after it is print.
       std::cout << " => price[" << n++ << "] = ";
       // Read the price from stdin (standard input or console). 
       std::cin >> price;
       if(!std::cin.good()){
          std::cerr << " [LOG] " << "User typed invalid input. Exit loop." << "\n";
          // Force exiting the loop 
          break;
       }
       sum += price;
        dataset.push_back(price);
       if(price > max)
          max = price;
       if(price < min)
          min = price;
    }
    // Clear stream error flag 
    std::cin.clear();

    std::cout << "Sum  = " << sum       << "\n";
    std::cout << "Mean = " << (sum / n) << "\n";
    std::cout << "Min  = " << min       << "\n";
    std::cout << "Max  = " << max       << "\n";

    std::cout << "Dataset = [ ";
    for(size_t i = 0; i < dataset.size(); i++)
       std::cout << " " << dataset[i] ;
    std::cout << "] \n";

    #ifdef _WIN32 
     std::cout << "\n" << "Type RETURN to quit." << "\n";
     std::cin.clear(); 
     std::cin.ignore(10, '\n');
     // Wait for single character 
     std::cin.get();
    #endif      
    return EXIT_SUCCESS;
};

Compiling:

$ clang++ basic-io.cpp -o basic-io.bin -g -std=c++1z -Wall -Wextra
# OR 
$ clang++ basic-io.cpp -o basic-io.bin -g -std=c++1z -Wall -Wextra && ./basic-io.bin 

Running:

$ ./basic-io.bin 
Enter the prices and type CTRL+D you are done.
 => price[0] = 20 
 => price[1] = 21.35
 => price[2] = 51.2
 => price[3] = 40.23
 => price[4] = 60.243
 => price[5] = 78
 => price[6] =  [LOG] User typed invalid input. Exit loop.
Sum  = 271.02
Mean = 38.72
Min  = 20.00
Max  = 78.00
Dataset = [  20.00 21.35 51.20 40.23 60.24 78.00] 

$ echo "30.12 40.23 100.34 90.23 52.0" | ./basic-io.bin 
Enter the prices and type CTRL+D you are done.
 => price[0] =  => price[1] =  => price[2] =  => price[3] =  => price[4] =  => price[5] =  [LOG] User typed invalid input. Exit loop.
Sum  = 312.92
Mean = 52.15
Min  = 30.12
Max  = 100.34
Dataset = [  30.12 40.23 100.34 90.23 52.00] 

1.8 TODO Functions/Procedures

1.8.1 Overview

C++ Functions has the following features:

  • Overload
    • Unlike in C, in C++, it is possible to define multiple functions implementation sharing the same name, although with the same type signature.
  • Default Parameters
    • C++ functions can have default parameters.
  • C++11 auto syntax
  • C++14 return type deduction

1.8.2 Simple function with all possible declarations

Example - Simple function => all possible declaration

Simple function which returns anything:

  • Alternative syntax 1:
void someFunction(){
     std::puts("I am a C++ free function");
}
  • Alternative syntax 2:
    • Note: The early return statement can be used for doing early return and avoiding many nested if-else statements.
void someFunction(){
     std::puts("I am a C++ free function");
     return;
}
  • Alternative syntax 3:
void someFunction(void){
     std::puts("I am a C++ free function");
     // return;       
}
  • Alternative syntax 4 (C++11):
auto someFunction(void) -> void {
     std::puts("I am a C++ free function");
     return;       
}
  • Alternative syntax 5 (C++14) - uses type deduction (aka type inference) for return type.
auto someFunction() {
     std::puts("I am a C++ free function");
     return;       
}

1.8.3 Functions with default parameters

  • Syntax 1:
double someFunction(double x, int n = 4, double z = 3.5 )
{
    std::cout << "  x = "  << x
              << " ; n = " << n
              << " ; z = " << z
              << "\n";
    return x * n + 4.0 * z;
}

// OR: 
double 
someFunction(double x, int n = 4, double z = 3.5 )
{
    std::cout << "  x = "  << x
              << " ; n = " << n
              << " ; z = " << z
              << "\n";
    return x * n + 4.0 * z;
}

// Testing: Cling REPL:

>> someFunction(3.0)
  x = 3 ; n = 4 ; z = 3.5
(double) 26.000000
>> 
>> someFunction(3.0, 10, 5)
  x = 3 ; n = 10 ; z = 5
(double) 50.000000
>> 
>> someFunction(4.0)
  x = 4 ; n = 4 ; z = 3.5
(double) 30.000000
>> 
  • Syntax 2 (C++11). In C++14, the return type can be omitted.
auto someFunction(double x, int n = 4, double z = 3.5) -> double
{
    std::cout << "  x = "  << x
              << " ; n = " << n
              << " ; z = " << z
              << "\n";
    return x * n + 4.0 * z;
}

>> someFunction(3.0)
  x = 3 ; n = 4 ; z = 3.5
(double) 26.000000

>> someFunction(4.0)
  x = 4 ; n = 4 ; z = 3.5
(double) 30.000000

>> someFunction(4.0, 10, 5)
  x = 4 ; n = 10 ; z = 5
(double) 60.000000
>> 

1.8.4 Function overload

Functions can be overloaded as long as they have unique type signature and the same return type.

#include <iostream> 

void show(){
     std::cout << "Call Version0." << "\n";
}

void show(int x){
     std::cout << "Call Version1 with =>" << " x [int] = " << x << "\n";
}

void show(double x){
     std::cout << "Call Version2 with =>" << " x [double] = " << x << "\n";
}

void show(const char* str){
     std::cout << "Call Version2 with =>" << " x [const char*] = " << str << "\n";
}

void show(int x, double z){
     std::cout << "Call Version4 with =>"
               << " x [int] = " << x
               << " z [double] = " << z
               << "\n";
}

void show(int x, double z, const char* str){
     std::cout << "Call Version5 with =>"
               << " x [int] = " << x
               << " z [double] = " << z
               << " std [const char*] = " << str
               << "\n";
}

Testing:

>> .L script.C

>> show()
Call Version0.

>> show(10)
Call Version1 with => x [int] = 10

>> show(3.1415)
Call Version2 with => x [double] = 3.1415

>> show(100, 3.1415)
Call Version4 with => x [int] = 100 z [double] = 3.1415

>> show("Function overload")
Call Version2 with => x [const char*] = Function overload

>> show(100, 3.1415, "overload")
Call Version5 with => x [int] = 100 z [double] = 3.1415 std [const char*] = overload
>> 

1.8.5 Functions with static variables - stateful functions

Variables inside of functions annotated with "static" keyword are similar to global variables (global static variables), they are initialized after the first function invocation and keeps their state during successive function invocations.

Features:

  • Initialized until first function invocation.
  • Lifetime: Entire program runtime, just like global variables, they are destroyed when the program execution ends.
  • Once initialized, keeps their state during function calls.
  • Stored in the program's and process' data segment.

Use cases:

Example:

  • Counter function with automatic variable (stack-allocated).
void counterAuto(){
   int x = 0;
   std::cout << "counter = " << x++ << "\n";
}

>> counterAuto()
counter = 0
>> counterAuto()
counter = 0
>> counterAuto()
counter = 0
>> counterAuto()
counter = 0
  • counter function with static variable.
void counterStatic(){
   static int x = 0;
   std::cout << "counter = " << x++ << "\n";
}

>> counterStatic()
counter = 0
>> counterStatic()
counter = 1
>> counterStatic()
counter = 2
>> counterStatic()
counter = 3
>> counterStatic()
counter = 4
>> 

1.9 String Manipulation

1.9.1 Class std::string

In C++, it is better to use std::string than the old "C-string" (const char*) since the class std::string is easier and safer to use than the old C-string class. In addition, std::string can grow or shrink at runtime automatically taking care of memory allocation.

Header:

  • <string>

Documentation:

Intialization:

#include <string>

// Create an empty string 
>> std::string s1
(std::string &) ""
>> 

>> std::string s2("I am a C++ std::string")
(std::string &) "I am a C++ std::string"
>> 
>> std::string s3 = "C++ CPP 20 is coming soon!!"
(std::string &) "C++ CPP 20 is coming soon!!"
>> 

>> const char* msg = "C++ strings";
>> std::string s4 {msg}
(std::string &) "C++ strings"
>> 

>> std::string s5(10, '-')
(std::string &) "----------"
>>

String size:

string::size - C++ Reference

>> std::string sx = "high performance computing - finite element methods";

>> sx.size()
(unsigned long) 51

>> sx.length()
(unsigned long) 51
>> 

String concatenation:

>> std::string s;
>> s
(std::string &) ""

>> s = s + " string " + " C++ " + " Move "
(std::__cxx11::basic_string &) " string  C++  Move "

>> s
(std::string &) " string  C++  Move "
>> s += " C " + " Assembly " + " HEX "

>> std::cout << "s = '" << s << "'" << "\n";
s = ' string  C++  Move  C '
>> 

String concatenation with append:

>> std::string s;
>> s
""
>> s.append("hello")
"hello"
>> s.append(" c++ ")
"hello c++ "
>> s.append(" std::string ")
"hello c++  std::string "
>> s
"hello c++  std::string "
>> 

Clear string:

>> std::string ss1 = "I am some string"
(std::string &) "I am some string"

>> ss1.clear()

>> ss1
(std::string &) ""

>> ss1.size()
(unsigned long) 0
>> 

Substring:

>> std::string str = "Early computers were designed for number crunching.";

>> str.substr(0, 10)
"Early comp"

>> str.substr(0, 15)
"Early computers"

>> str.substr(20, str.length())
" designed for number crunching."

String comparison:

>> std::string pass;
>> pass
(std::string &) ""
>>
>> pass == "access"
(bool) false
>> pass = "access"
"access"
>> pass == "access"
(bool) true
>> 

>> std::string("hello") == "world"
(bool) false
>> std::string("hello") == "hello"
(bool) true
>>  

void checkPassword(const std::string& passwd){
   if(passwd == "87afx")
           std::puts("Access granted. OK.");
   else
           std::puts("Access denied. FAILURE.");
}

>> checkPassword("aa")
Access denied. FAILURE.
>> checkPassword("")
Access denied. FAILURE.
>> checkPassword("87afx")
Access granted. OK.
>> 

Reverse string:

>> std::string ssa = "hello world string";
>> ssa
(std::string &) "hello world string"

>> std::reverse(std::begin(ssa), std::end(ssa))
>> ssa
(std::string &) "gnirts dlrow olleh"

>> std::reverse(ssa.begin(), ssa.end())
>> ssa
(std::string &) "hello world string"
>> 

1.9.2 Concatenate string literals

Example: concatenate string literas and pass the to the function perror of signature:

void std::perror(const char*);

Trial:

const char* s1 = " world ";
const char* s2 = " C++ ";
const char* s3 = " strings ";

// Compilation error:
>> std::perror( " literal1 " + "literal 2" + s1 + s2 + s3);
ROOT_prompt_4:1:27: error: invalid operands to binary expression ('const char *' and 'const char *')
std::perror( " literal1 " + "literal 2" + s1 + s2 + s3);

Solution: Wrap the first argument into an std::string. The method .data() from the std::string class returns a pointer of type (const char*) or a C-null terminated character array.

>> std::string text = std::string(" literal1 ") + "literal 2" + s1 + s2 + s3;
>> text
(std::string &) " literal1 literal 2 world  C++  strings "
>> 
>> std::perror(text.data())
 literal1 literal 2 world  C++  strings : No such file or directory
>> 

1.9.3 C++14 String literals and C++11 Raw string literals

C++14 String literals

In C++, it is not possible to concatenate or compare string literals directly using only operators without any function such as strcmp. Example CLING REPL:

Documentation: operator""s - cppreference.com

$ ~/opt/cling_2018-09-16_fedora27/bin/cling -std=c++17

#include <iostream>
#include <string>

[cling]$ std::string s = "concatenate " + "string"
input_line_8:2:33: error: invalid operands to binary expression ('const char *' and 'const char *')
 std::string s = "concatenate " + "string"
                 ~~~~~~~~~~~~~~ ^ ~~~~~~~~
[cling]$ 

[cling]$ "hello" == "hello"
input_line_15:2:10: warning: result of comparison against a string literal is unspecified
      (use strncmp instead) [-Wstring-compare]
 "hello" == "hello"
 ~~~~~~~ ^

C++14 provides string literal suffix operator (s) which can simplify string concatenation and comparison:

$ ~/opt/cling_2018-09-16_fedora27/bin/cling -std=c++17
#include <iostream>
#include <string>
using namespace std::string_literals;

[cling]$ auto a_std_string = "this is an std::string object"s
"this is an std::string object"

[cling]$ typeid(a_std_string) == typeid(std::string)
(bool) true

// Instead of: 
[cling]$ s = std::string("concatenate ") + "string"
"concatenate string"

// The string literal operator (s) makes this code shorter:
[cling]$ auto s2 = "concatenate "s + "string"
"concatenate string"

String literal comparison can be peformed in two ways, one is using the function strcmp and other one is by wrapping the first literal with the constructor std::string(const char*). The last form of comparison can be simplified using the suffix operator (s):

#include <cstring>

const char* str1 = "c++ ASM";
const char* str2 = "Fortran";

[cling]$ std::string("c++ ASM") == str1
(bool) true

[cling]$ std::string("c++ ASM") == str2
(bool) false

// === Shorter alternative ===========//
[cling]$ "c++ ASM"s == str1
(bool) true
[cling]$ "c++ ASM"s == str2
(bool) false
[cling]$ 

C++11 Raw string literals

Raw string literals allows writing multi line strings, regular expressions (regex) without boilerplate and not escaped strings with backward slashes such as Windows paths.

Form:

R"(<some string ...>)"

Example:

  • file: string-literal.cpp
#include <iostream>
#include <string>

int main()
{
       std::cout << std::boolalpha;
       std::cerr << std::boolalpha;

       // Raw string literals does not need escaping. 
       std::string raw1 = R"(C:\Users\nobody\tests\path)";  

       auto raw2 = std::string(R"(C:\Users\nobody\tests\path)");

       const char* raw3 = R"(
  This is a multiline 
  string
  path = C:\Users\somebody\else\docs
\n  New line char - LF (Line Feed - LF)   
\r  New line char - CR (Carriage Return)
\t  Tab char
\b  Backspace character 
       )";
       std::cout << "raw 1 = " << raw1 << std::endl;        
       std::cout << "raw 2 = "  << raw2 << std::endl;
       std::cout << "raw 3 = " << raw3 << std::endl;
       std::cout << "typeid(raw2) == typeid(std::string) ? "
                         << (typeid(raw2) == typeid(std::string))
                         << std::endl;

       return EXIT_SUCCESS;
}

Output:

$ clang++ -std=c++1z -g -Wall -Wextra cpp14-string-literals.cpp -o cpp14-string-literals.bin 
$ ./cpp14-string-literals.bin

raw 1 = C:\Users\nobody\tests\path
raw 2 = C:\Users\nobody\tests\path
raw 3 = 
   This is a multiline 
   string
   path = C:\Users\somebody\else\docs
 \n  New line char - LF (Line Feed - LF)   
 \r  New line char - CR (Carriage Return)
 \t  Tab char
 \b  Backspace character 

typeid(raw2) == typeid(std::string) ? true

Without the raw string literal operator the Windows path raw1 would have to be written in following way.

std::string raw1 = "C:\\Users\\nobody\\tests\\path";    

1.9.4 Numerical Parsers Functions (C++11)

Header: <string>

Documentation:

Functions:

Functions Convert to:
stoi string to integer
stol Convert string to long int
stoul Convert string to unsigned integer
stoll string to long long
stoull unsigned long long
stof Convert string to float
stod Convert string to double
stold Convert string to long double
  • std::stoi
    • Convert a string to integer and throws an exception, if the operation is not successful.
>> std::string s = "200"
(std::string &) "200"

>> std::stoi(s)
(int) 200
>> s = "-400"
"-400"

>> s = "-400";
>> std::stoi(s)
(int) -400

// Throws exception 
>> s = "aaabb -400";
>> std::stoi(s)
Error in <TRint::HandleTermInput()>: std::invalid_argument caught: stoi
>> 

//  Read hexadecimal number 
>> s = "0xFFAB";
>> std::stoi(s, nullptr, 16)
(int) 65451
>> 

>> std::stoi("0xFF", nullptr, 16)
(int) 255
>> std::stoi("0xFFFF", nullptr, 16)
(int) 65535
>> 
  • std::stod
    • Convert a string to double. (double precision float point from IEEE754)
>> std::string s = "21.4e3";
>> std::stod(s)
(double) 21400.000

>> s = "  .4e-6";
>> std::stod(s)
(double) 4.0000000e-07
>> 

// Throw exception and terminates program, if the exception is not caught.
>> s = "failure case  .4e-6";
>> std::stod(s)
Error in <TRint::HandleTermInput()>: std::invalid_argument caught: stod
>> 

>> s = "NAN";
>> std::stod(s)
(double) nan
>> s = "INFINITY";
>> std::stod(s)
(double) inf
>> s = "-INFINITY";
>> std::stod(s)
(double) -inf
>> 
>> s = "3.1415";
>> std::stod(s)
(double) 3.1415000
>> 

>> std::stod("1.23e3")
(double) 1230.0000
>> 

1.9.5 Function to_string (C++11)

Note: This function is useful for concatenating numbers and string.

Documentation:

Overloads:

tring to_string (int val);
string to_string (long val);
string to_string (long long val);
string to_string (unsigned val);
string to_string (unsigned long val);
string to_string (unsigned long long val);
string to_string (float val);
string to_string (double val);
string to_string (long double val);

Example:

>> std::string s;
>> 
>> double x = 3.1415;

>> s = std::string("The value of x = ") + std::to_string(x)
 "The value of x = 3.141500"
>> 

>> s.clear()
>> int n = 100;
>> s = std::string("The value of n (integer) = ") + std::to_string(n)
"The value of n (integer) = 100"
>> 

1.9.6 Printing to string with std::stringstream

Header: <sstream>

Other useful headers:

  • <iomanip> - Stream manipulators, std::setw, std::setprecision and so on.
  • <ostream> - Class std::ostream, generic output.

Documentation:

The class std::stringstream is a better option for concatenating strings or printing to string.

#include <iostream> 
#include <iomanip>
#include <sstream>

>> std::stringstream ss;
>> 

>> std::stringstream ss;
>> ss << "Hello world" << "\n";
>> ss << "PI = " << M_PI << "\n";
>> ss << std::fixed << std::setprecision(3) << "g = " << 9.81723 << "\n";

// Get std::string from ss
>> ss.str()
"Hello world
PI = 3.14159
g = 9.817
"
>> 

>> std::string s_copy = ss.str()
(std::string &) "Hello world
PI = 3.14159
g = 9.817
"
>> 

// Clear string stream 
>> ss.str(); ss.str("");
>> ss.str()
""
>> 

Example: Polymorphic function which can print to stdout (cout), stderr (cerr) or to string.

void printReport(std::ostream& os){
  os << std::setprecision(3);
  os << std::fixed;
  os << std::boolalpha;
  char nl = '\n';
  os << "Report Data" << nl
     << " x = " << 23.123 << nl
     << " d = " << true   << nl
     << " z = " << M_PI   << nl;
  os.flush();
}

Print to stdout and stderr:

// Print to stdout (Standard output)
>> printReport(std::cout)
Report Data
 x = 23.123
 d = true
 z = 3.142

// Print to stderr (Standard error output)
>> printReport(std::cerr)
Report Data
 x = 23.123
 d = true
 z = 3.142
>> 

Print to string stream:

// Print to string stream 
>> std::stringstream ss;
>> printReport(ss)

>> ss.str()
 "Report Data
 x = 23.123
 d = true
 z = 3.142
"

>> std::cout << ss.str() << '\n';
Report Data
 x = 23.123
 d = true
 z = 3.142

>> printReport(ss)
>> std::cout << ss.str() << '\n';
Report Data
 x = 23.123
 d = true
 z = 3.142
Report Data
 x = 23.123
 d = true
 z = 3.142

1.9.7 TODO Parsing strings with std::stringstream

Header: <sstream>

Documentation:

Example 1: Parse integers from string.

#include <iostream>
#include <sstream>
#include <string>

std::string data = " 200 400  50.232 100 200 440";
auto ss = std::stringstream{data};
double x;

size_t i = 0;
while(!ss.eof() && !ss.fail()){
   ss >> x;
   std::cout << "x[" << i << "] = " << x << "\n";
   i++;
}

Output:

x[0] = 200
x[1] = 400
x[2] = 50.232
x[3] = 100
x[4] = 200
x[5] = 440

Example 2: Extract all unique words from a given line:

#include <iostream>
#include <sstream>
#include <string>

std::string line;
line =  "hex binary data always key hard narrow path bug hex";
line += " binary data path words key";

auto words = std::set<std::string>{};
auto ss = std::stringstream{line};
std::string w;
while(!ss.eof() && ss.good()){
  ss >> w;
  words.insert(w);
}

>> line
(std::string &) "hex binary data ... binary data path words key"
>> 

>> words
{ "always", "binary", "bug", "data", "hard", "hex", "key", "narrow", "path", "words" }
>> 

>> for(const auto& w: words) { std::cout << w << "\n";}
always
binary
bug
data
hard
hex
key
narrow
path
words
>> 

Example 3: Extract lines from string.

std::string text;
text += "C++ => FEM -> Finite Element Methods\n";
text += "C++ => High Performance Computing\n";
text += "C++ => Monte Carlo Simulations\n";

>> std::cout << text << "\n";
C++ => FEM -> Finite Element Methods
C++ => High Performance Computing
C++ => Monte Carlo Simulations

std::deque<std::string> lines;
std::string line;
// Input Stream                                                                
auto is = std::stringstream {text};

while(std::getline(is, line)) lines.emplace_back(line);

>> lines
{ "C++ => FEM -> Finite Element Methods", "C++ => High Performance Computing", "C++ => Monte Carlo Simulations" }
>> 

>> for(const auto& lin: lines) { std::cout << " + line = " << lin << "\n"; }
 + line = C++ => FEM -> Finite Element Methods
 + line = C++ => High Performance Computing
 + line = C++ => Monte Carlo Simulations
>> 

Or, putting it all together:

  • Version: read lines from string
// Requires: <string> <sstream> and <iostream>                                 
auto splitLines1(const std::string& text)
  -> std::deque<std::string>
{
  auto lines = std::deque<std::string>{};
  auto line  = std::string{};
  auto is    = std::stringstream{text};
  while(std::getline(is, line))
    lines.emplace_back(line);
  return lines;
}

std::string text;
text += "=> FEM -> Finite Element Methods\n";
text += "=> High Performance Computing\n";
text += "=> Monte Carlo Simulations\n";
text += "=> Global Markets Trends\n";
text += "=> Growth forecast survery.\n";

>> for(const auto& lin: splitLines1(text)) { std::cout << " +=> " << lin << "\n"; }
 +=> => FEM -> Finite Element Methods
 +=> => High Performance Computing
 +=> => Monte Carlo Simulations
 +=> => Global Markets Trends
 +=> => Growth forecast survery.
>> 

Example 4: Extract lines from input stream.

  • Read lines from input stream - this second version can read from any sublcass of std::istream, thus, it can also get lines from files and starndard input stream std::cin (console or stdin).
// Read all lines from stram to container Requires: <string> <sstream>
// and <iostream>
auto splitLines2(std::istream& is) -> std::deque<std::string>
{
  std::cerr << " [INFO] L-value reference."
  auto lines = std::deque<std::string>{};
  auto line  = std::string{};
  while(std::getline(is, line))
    lines.emplace_back(line);
  return lines;
}
// Overload to work with temporary objects (R-values)
auto splitLines2(std::istream&& is) -> std::deque<std::string>
{
  std::cerr << " [INFO] R-value reference."
  auto lines = std::deque<std::string>{};
  auto line  = std::string{};
  while(std::getline(is, line))
    lines.emplace_back(line);
  return lines;
}

Running:

  • Read lines from string
std::string text;
text += "=> FEM -> Finite Element Methods\n";
text += "=> High Performance Computing\n";
text += "=> Monte Carlo Simulations\n";
text += "=> Global Markets Trends\n";
text += "=> Growth forecast survery.\n";

auto ss = std::stringstream(text) ;

// Read from an L-value (object bounded to a memory location)
>> auto ss = std::stringstream(text) ;
>> auto lines = splitLines2(ss);
 [INFO] L-value reference.

>> lines2
{ "=> FEM -> Finite Element Methods", "=> High Performance Computing", ...}
>> 

// Read from an R-value (temporary object)
>> auto lines = splitLines2(std::stringstream(text));
 [INFO] R-value reference.
>> lines
 { "=> FEM -> Finite Element Methods", "=> High Performance Computing", ...}
>> 
  • Read lines from file
// Read lines from file: 
>> res = splitLines2(std::ifstream("/etc/protocols"));
 [INFO] R-value reference.

>> res.size()
(unsigned long) 162
>> 

>> for(const auto& lin: res) { std::cout << " + line = " << lin << "\n";}
 + line = # /etc/protocols:
 + line = # $Id: protocols,v 1.12 2016/07/08 12:27 ovasik Exp $
 + line = #
 + line = # Internet (IP) protocols
 + line = #
 + line = # from: @(#)protocols 5.1 (Berkeley) 4/17/89
 + line = #
 + line = # Updated for NetBSD based on RFC 1340, Assigned Numbers (July 1992).
 + line = # Last IANA update included dated 2011-05-03
 + line = #
   ... ... ... ... .... 
  • Read lines from console
// Types many line and then  CTRL + D (Send EOF - End Of File) when you are done.
>> auto res2 = splitLines2(std::cin);
 [INFO] L-value reference.
hello world
c++ C++11 is awesome
C++17 is amazing
C++20 RULEZ! High peformance technical computing
  Monte Carlo Methods
  High Energy Physics
  Electronic Engineering - Embedded Processors    
>> 

>> res2
 { "hello world", "c++ C++11 is awesome", "C++17 is amazing", ...}
>> 

>> auto res2copy = std::vector<std::string>(res2.begin(), res2.end())
 { "hello world", "c++ C++11 is awesome", "C++17 is amazing", ...}
>> 

>> for(const auto& lin: res2){ std::cout << " [+] line = " << lin << "\n";}
 [+] line = hello world
 [+] line = c++ C++11 is awesome
 [+] line = C++17 is amazing
 [+] line = C++20 RULEZ! High peformance technical computing
 [+] line =   Monte Carlo Methods
 [+] line =   High Energy Physics
 [+] line =   Electronic Engineering - Embedded Processors
>> 

1.9.8 Transform string to uppercase or lowercase

Headers:

  • <string>
  • <cstring>
  • <algorithm>

Convert string to upper case:

>> std::string text = "c++ is suitable for system programming";
>> 
std::transform( text.begin(),
                text.end(),
                text.begin(),
                [](char x){ return std::toupper(x); }
                );
>> text
(std::string &) "C++ IS SUITABLE FOR SYSTEM PROGRAMMING"
>>

auto toUpper(const std::string& text) -> std::string {
  std::string out = text;
  std::transform( out.begin(),
                  out.end(),
                  out.begin(),
                  [](char x){ return std::toupper(x); }
                  );
  return out;
}

>> toUpper("c++ system programming embedded high performance")
(std::string) "C++ SYSTEM PROGRAMMING EMBEDDED HIGH PERFORMANCE"
>> 

Convert string to lower case:

std::string text = "c++ IS Suitable foR System proGramMing";
std::transform( text.begin(),
                text.end(),
                text.begin(),
                [](char x){ return std::tolower(x); }
                );

>> text
(std::string &) "c++ is suitable for system programming"
>> 

1.9.9 Check whether a string contains substring

bool containsStr(const std::string& str, const std::string& substr){
     return str.find(substr) != std::string::npos;
}

>> containsStr("cpp plus plus cee c++ 34", "c++")
(bool) true
>> containsStr("cpp plus plus cee c++ 34", "cpp11")
(bool) false
>> containsStr("cpp plus plus cee c++ 34", "cee")
(bool) true
>> 

1.9.10 Search and replace

auto replaceStr( const std::string& text,                              
                 const std::string& rep,                                
                 const std::string& subst                               
              ) -> std::string {                                                     
  // Copy paraemter text (Invoke copy constructor)                             
  std::string out = text;                                                      
  // Find position of character matching the string                            
  size_t i = out.find(rep);                                                    
  while(i != std::string::npos){                                               
    out.replace(i, rep.size(), subst);                                         
    i = out.find(rep, i);                                                      
  }                                                                            
  return out;                                                                  
} 

>> replaceStr(" c++11 high c++future c++15 peformance cpp c++17", "c++", "")
(std::string) " 11 high future 15 peformance cpp 17"

>> replaceStr(" c++11 high c++future c++15 peformance cpp c++17", "c++", "DLang")
(std::string) " DLang11 high DLangfuture DLang15 peformance cpp DLang17"
>> 

>> replaceStr(" c++11 high c++future c++15 peformance cpp c++17", "c++", "CXX")
(std::string) " CXX11 high CXXfuture CXX15 peformance cpp CXX17"

1.9.11 Character Testing

Function Return
int isalphanum(int) Non-zero if input is letter or digit
int isalpha(int) Non-zero if input is letter
int iscntr(int) Non-zero if input is control character
int isdigit(int) Non-zero if input is digit
int isgraph(int) Non-zero if input is not whitespace printable character
int isprint(int) … if input is printable character
int ispunct(int) … if input is neither alphanumeric nor whitespace
int isspace(int) … if input is whitespace
int isxdigit(int) … if input is hexadecimal digit
  • Check whether string is number:
#include <string>
#include <algorithm>

bool isNumber(const std::string& str){
  if(str == "") return false;
  return std::all_of(
             str.begin(),
             str.end(),
             [](char x){
               return std::isdigit(x);
             });
}

>> isNumber("1213")
(bool) true
>> isNumber("12x13")
(bool) false
>> isNumber("")
(bool) false
>> 
  • Check whether string is an hexadecimal number
bool isNumberHex(const std::string& str){
   if(str == "") return false;
   return std::all_of(
              str.begin(),
              str.end(),
              [](char x){
                return std::isxdigit(x);
              });
 }

 >> isNumberHex("0fabcd92813")
 (bool) true
 >> 
 >> isNumberHex("0fxabcd92813")
 (bool) false
 >> isNumberHex("")
 (bool) false
 >> isNumberHex("ff")
 (bool) true
 >> isNumberHex("0xff")
 (bool) false
  • Check whether string contains only letters
bool isAlphaOnly(const std::string& str){
  if(str == "") return false;
  return std::all_of(
             str.begin(),
             str.end(),
             [](char x){
               return std::isalpha(x);
             });
}

>> isAlphaOnly("helloworld")
(bool) true
>> isAlphaOnly("hello54world")
(bool) false
>> isAlphaOnly("")
(bool) false
>> isAlphaOnly("w")
(bool) true
>> isAlphaOnly("sa.ds-a")
(bool) false
>> 

1.9.12 Split string by delimiter

Testing:

>> std::string t = "key =  value";
>> size_t i = t.find("=")
(unsigned long) 4

>> t.substr(0, i)
"key "
>> 
>> t.substr(i, t.size())
"=  value"
>> t.substr(i + 1, t.size())
"  value"
>> 

Encapsulating into a function:

#include <string>
#include <deque>

auto splitString(const std::string& str,
                 const std::string& delim
                 ) -> std::deque<std::string> {
  std::deque<std::string> xs;
  size_t i = str.find(delim);
  if(i == std::string::npos){
    xs.push_back("");
    xs.push_back("");
    return xs;
  }
  xs.push_back(str.substr(0,    i));
  xs.push_back(str.substr(i + 1, str.size()));
  return xs;
};

>> splitString("key value", "=")
(std::deque<std::string>) { "", "" }
>> splitString("key = value", "=")
(std::deque<std::string>) { "key ", " value" }
>> splitString("key  va=lue", "=")
(std::deque<std::string>) { "key  va", "lue" }
>> splitString("key  value=", "=")
(std::deque<std::string>) { "key  value", "" }
>> splitString("", "=")
(std::deque<std::string>) { "", "" }
>> splitString("key  value=", "")
(std::deque<std::string>) { "", "ey  value=" }
>> 

1.9.13 Extract quoted string (C++14)

Documentation: std::quoted - cppreference.com

#include <iostream>
#include <string>
#include <sstream>
#include <iomanip> // std::quoted in C++14

std::string s = " 200 key value \" aquoted string \"  \"next quote\"";
std::stringstream ss{s};

std::string out;

while(!ss.eof()){
  ss >> std::quoted(out);
  std::cout << "word = " << out << "\n";
}

Output:

word = 200
word = key
word = value
word =  aquoted string 
word = next quote
word = next quote

Hand-rolled quoted string parser:

  • Useful if C++14 is not avaiable.
#include <iostream>
#include <sstream>
#include <string>

// Requires headers: <string> and <sstream>
auto readQuoted(std::istream& is) -> std::string {
     char ch;
     std::ostringstream so;
     if(is.eof()) return "";
     // Find first quote:
     //-------------------------------------
     do {
        ch = static_cast<char>(is.get());
     } while(is.good() && !is.eof() && std::isspace(ch));   
     //std::cout << "ch = " << ch  << "\n"; 
     if(ch != '\"'){
        so << ch;
        do {
            ch = static_cast<char>(is.get());
            so << ch;
        } while(is.good() && !is.eof() && !std::isspace(ch));
        if(is.eof()) return "";
        return so.str();
     }
     //std::cout << "ch = " << ch  << "\n"; 
     do {
        ch = static_cast<char>(is.get());
        if(ch != '\"') so << ch;
     } while(is.good() && !is.eof() && ch != '\"');
     if(is.eof()) // Did not find a matching quote.
        return "";
     //std::cout << "ch = " << ch  << "\n";
     return so.str();
}

Running:

std::string s = "  \" 200 99 888 quoted - string \" KEY \" VALUE PAIR \"  100 \" Sao Paulo City\" 4500 key ";
std::stringstream ss{s};

>>> while(ss.good()) std::cout << "quoted = " << readQuoted(ss) << "\n";
quoted =  200 99 888 quoted - string 
quoted = KEY 
quoted =  VALUE PAIR 
quoted = 100 
quoted =  Sao Paulo City
quoted = 4500 
quoted = key 
quoted = 

1.9.14 Parse comma separated value - CSV row

Note: the following CSV parser functions fails for heterogeneous data or when there are quotes characters. It is better to use a proper CSV parser library as CSV is not a standardized data exchange format and has lots of tricky edge cases.

  • Example: Simple CSV line parser.
#include <iostream>
#include <vector>
#include <string>
#include <sstream>

std::vector<double> parseCSVRow(const std::string& line, char delim = ','){
  auto out   = std::vector<double>{};
  auto token = std::string{};
  auto ss    = std::stringstream{line};
  while(std::getline(ss, token, delim)){
    out.push_back(std::stod(token));
  }
  return out;
}

Testing:

std::string raw_data = "100,   200,  400, -100.23, 60.23, 90.32";

>> parseCSVRow(raw_data)
(std::vector<double>) { 100.00000, 200.00000, 400.00000, -100.23000, 60.230000, 90.320000 }
>> auto xs = parseCSVRow(raw_data);
>> xs[0]
(double) 100.00000
>> xs[1]
(double) 200.00000
>> xs[2]
(double) 400.00000
>> xs[3]
(double) -100.23000
>> 
  • Example: Generic CSV line Parser.
#include <iostream>
#include <vector>
#include <string>
#include <sstream>

template<class Type = double>
std::vector<Type> parseCSVRowGeneric(const std::string& line,
                                     char delim = ','
                                     ){
  auto out   = std::vector<Type>{};
  auto token = std::string{};
  auto ss    = std::stringstream{line};
  auto is    = std::stringstream{};
  Type value{};
  while(std::getline(ss, token, delim)){
    // Clear error flags                                                                                                                              
    is.clear();
    // Set is stream value to token                                                                                                                   
    is.str(token);
    // Extract from 'is' stream                                                                                                                       
    is >> value;
    out.push_back(value);
  }
  return out;
}

Testing:

// Test data
std::string lineB = "2002.23, 434.34, 90.23, -23.23, 1e3";
std::string lineC = "100; 200; 300; 150; 500; 600";
std::string lineD = "Apple: oranges : juice beans : rice";

>> parseCSVRowGeneric(lineB)
(std::vector<double>) { 2002.2300, 434.34000, 90.230000, -23.230000, 1000.0000 }

>> parseCSVRowGeneric<>(lineB)
(std::vector<double>) { 2002.2300, 434.34000, 90.230000, -23.230000, 1000.0000 }
>> 

>> parseCSVRowGeneric<double>(lineB, ',')
(std::vector<double>) { 2002.2300, 434.34000, 90.230000, -23.230000, 1000.0000 }
>> 

>> parseCSVRowGeneric<int>(lineC, ';')
(std::vector<int>) { 100, 200, 300, 150, 500, 600 }
>> 

>> parseCSVRowGeneric<std::string>(lineD, ':')
{ "Apple", "oranges", "juice", "rice" }

1.9.15 C++11 Regular Expressions Regex

  1. Overview

    C++11 STL provides a regular expression library with ECMAScript (Javascript) engine. It makes easier to use regular expressions without third-party libraries.

    Documentation:

    ECMAScript Regex Syntax

    Engines

    Regular expression grammars supported by the C++11 regex library:

    • ECMAScript ("JavaScript" is an Oracle inc. trademark) - default regex engine
    • basic
    • extended
    • awk
    • grep
    • egrep

    Classes:

    Class Description
    std::regex Regular expression object. It is the same as std::basic_regex
       
    Regex Matching  
    std::match Regex matching results. It is the same as std::match_results
    std::smatch std::match_results<std::string::const_iterator>
    std::cmatch std::match_results<const char*>
    std::wcmatch std::match_results<const wchar_t*>
    std::wsmatch std::match_results<std::wstring::const_iterator>
       
    Iterators  
    std::regex_iterator<T> Regex iterator object - provides access to regex matches.
    cregex_iterator regex_iterator<const char*> - Specialization for type const char*
    wcregex_iterator regex_iterator<const wchar_t*> - Specialization for type const wchar_t*
    sregex_iterator regex_iterator<std::string::const_iterator>
    wsregex_iterator regex_iterator<std::wstring::const_iterator>

    Online REGEX Testers

  2. Example

    Compile and run:

    $ clang++ regex1.cpp -o regex1.bin -std=c++1z -O0 -Wall && ./regex1.bin
    $ g++ regex1.cpp -o regex1.bin -std=c++1z -O0 -Wall && ./regex1.bin
    
    # Compilation is slow: it takes 2 seconds [Non optimized]!!
    $ time clang++ regex1.cpp -o regex1.bin -std=c++1z -O0 -Wall
    real    0m2.356s
    user    0m2.200s
    sys     0m0.112s
    
    # Compilation time is even slower when optimized!! 6 seconds to build!
    $ time clang++ regex1.cpp -o regex1.bin -std=c++1z -O3 -Wall
    real    0m6.027s
    user    0m5.584s
    sys     0m0.107s
    

    Macro DBG_DISP for displaying expressions:

    #define DBG_DISP(expr)  std::cerr << __FILE__ << ":" << __LINE__ << ":" \
            << " ; " <<  #expr << " = " << (expr)  <<  std::endl
    

    Headers

    #include <iostream>
    #include <string>
    #include <regex>
    

    Experiment 1: Match entire string

    std::puts("\n ======== EXPERIMENT 1 === Match entire string =====");
    std::regex numRegex("\\d+");        
    DBG_DISP(std::regex_match("3423", numRegex));
    DBG_DISP(std::regex_match("AAB786", numRegex));
    DBG_DISP(std::regex_match("  9745", numRegex));
    DBG_DISP(std::regex_match(" 634 ", numRegex));
    

    Output:

     ======== EXPERIMENT 1 === Match entire string =====
    regex1.cpp:17: ; std::regex_match("3423", numRegex) = true
    regex1.cpp:18: ; std::regex_match("AAB786", numRegex) = false
    regex1.cpp:19: ; std::regex_match("  9745", numRegex) = false
    regex1.cpp:20: ; std::regex_match(" 634 ", numRegex) = false
    

    Experiment 2: Check whether text has regex

    std::puts("\n ======== EXPERIMENT 2 === Check whether text has regex =====");
    std::regex variable("\\s?[a-zA-Z][a-zA-Z|\\d]*\\b");
    DBG_DISP(std::regex_search("3423", variable));
    DBG_DISP(std::regex_search("dot.com\\ -buble hello variable4561 ", variable));
    DBG_DISP(std::regex_search("  -dot.com.buble.burst ", variable));
    

    Output:

     ======== EXPERIMENT 2 === Check whether text has regex =====
    regex1.cpp:25: ; std::regex_search("3423", variable) = false
    regex1.cpp:26: ; std::regex_search("dot.com\\ -buble hello variable4561 ", variable) = true
    regex1.cpp:27: ; std::regex_search("  -dot.com.buble.burst ", variable) = true
    

    Experiment 3: Extract text

    std::puts("\n ======== EXPERIMENT 3 === Extract text =====");
    std::regex phoneRe{"phone:\\s*(\\d{8})\\s*$"};
    std::string line;
    std::smatch result;
    
    line = "phone:85619751";
    if(std::regex_search(line, result, phoneRe) && result.size() > 1)
       std::cout << "Result1 = " << result.str(1) << "\n";
    else
       std::cout << "Result1 = <<NOT MATCH>>" << std::endl;
    
    line = "phone:  65658741  ";
    if(std::regex_search(line, result, phoneRe) && result.size() > 1)
        std::cout << "Result2 = " << result.str(1) << "\n";
    else
        std::cout << "Result2 = <<NOT MATCH>>" << std::endl;
    
    line = "phone:  756596213434";
    if(std::regex_search(line, result, phoneRe) && result.size() > 1)
       std::cout << "Result3 = " << result.str(1) << "\n";
    else
       std::cout << "Result3 = <<NOT MATCH>>" << std::endl;
    
    line = "phone:  ABBX621";
    if(std::regex_search(line, result, phoneRe) && result.size() > 1)
       std::cout << "Result4 = " << result.str(1) << "\n";
    else
       std::cout << "Result4 = <<NOT MATCH>>" << std::endl;     
    

    Output:

     ======== EXPERIMENT 3 === Extract text =====
    Result1 = 85619751
    Result2 = 65658741
    Result3 = <<NOT MATCH>>
    Result4 = <<NOT MATCH>>
    

    Experiment 4-A: Finding all regex matches

    std::puts("\n ======== EXPERIMENT 4-A === Finding all regex matches  ======");      
    std::regex nregex("[0-9]+\\b", std::regex::ECMAScript); 
    std::string line = " 565 923 100 -934  AABB56 0835";
    std::sregex_iterator it_beg(line.begin(), line.end(), nregex);
    std::sregex_iterator it_end;
    std::cout << " Number of matches = " << std::distance(it_beg, it_end) << "\n";
    int i = 0;
    for(auto it  = it_beg; it != it_end; it++){ 
       std::cout << " => x[" << i++ << "] = " << it->str() << "\n";
    }
    

    Output:

    ======== EXPERIMENT 4-A === Finding all regex matches  ======
    Number of matches = 6
    => x[0] = 565
    => x[1] = 923
    => x[2] = 100
    => x[3] = 934
    => x[4] = 56
    => x[5] = 0835
    
    

    Experiment 4-B: Finding all regex matches

    std::puts("\n ======== EXPERIMENT 4-B === Finding all regex matches  ======");
    // Matches 4 letters followed by 3 digits
    // first capture group 4 letters set, second capture group: 3 digits set 
    std::regex re("([A-Z]{4})([0-9]{3})");  
    std::string line = " ABCD367 XYAZ561 baa 341 MNPQ456 ";
    std::sregex_iterator begin(line.begin(), line.end(), re);
    std::sregex_iterator end;
    for(auto it  = begin; it != end; it++)
        std::cout << " capture[0] = "   << it->str() 
                  << " ; capture[1] = " << it->str(1)
                  << " ; capture[2] = " << it->str(2)
                  << "\n";
    

    Output:

    ======== EXPERIMENT 4-B === Finding all regex matches  ======
    capture[0] = ABCD367 ; capture[1] = ABCD ; capture[2] = 367
    capture[0] = XYAZ561 ; capture[1] = XYAZ ; capture[2] = 561
    capture[0] = MNPQ456 ; capture[1] = MNPQ ; capture[2] = 456
    

    Experiment 4-C: Finding all regex matches

    std::puts("\n ======== EXPERIMENT 4-C === Finding all regex matches range-based loop ===");
    // Matches 4 letters followed by 3 digits
    // first capture group 4 letters set, second capture group: 3 digits set 
    std::regex re("([A-Z]{4})([0-9]{3})");  
    std::string line = " ABCD367 XYAZ561 baa 341 MNPQ456 ";
    std::smatch matches;
    std::regex_search(line, matches, re);
    for(const auto& m : matches )
       std::cout << " =>>> "   << m << "\n";
    

    Output:

    ======== EXPERIMENT 4-C === Finding all regex matches range-based loop ===
    =>>> ABCD367
    =>>> ABCD
    =>>> 367
    

    Experiment 5: Replace all matches

    std::puts("\n ======== EXPERIMENT 5 === Replace matches  ======");
    // Matches 4 letters followed by 3 digits
    // first capture group 4 letters set, second capture group: 3 digits set 
    std::regex re("([A-Z]{4})([0-9]{3})");  
    std::string line = " ABCD367 XYAZ561 baa 341 MNPQ456 ";
    std::string out = std::regex_replace(line, re, "{$1=$2} - ");
    std::cout << " out = " << out << std::endl;
    

    Output:

    ======== EXPERIMENT 5 === Replace matches  ======
    out =  {ABCD=367} -  {XYAZ=561} -  baa 341 {MNPQ=456} -  
    

1.10 C++11 - Uniform Initialization

The C++11 uniform initialization feature allows classes, STL containers (collections) and primitives values to be initialized in a uniform way similar to vectors and C-arrays.

  • Uniform initialization of primitive types:
int    x {};   // x = 0
double fd {};  // fd = 0.0
float  ff {};  // ff = 0.0f
bool   flag{}; // flag = false = 0 
  • Uniform initialization of C-arrays
int xs[] {10, 20, 30};  //  int xs[] =  {10, 20, 30};
double ys[3] {1.0};     //  double ys[] = {1.0};
  • Uniform initialization of pointers
double* fdptr{} ; // fdptr = nullptr 
  • Uniform initialization of STL containers:
std::string s {};                // s = ""
std::string s {"Hello world"};   // s = "Hello World"

// Vectors 
std::vector<double> xs {};                      
std::vector<double> ys {2.0, 3.0, 4.0, 5.0};
std::vector<string> zs {"apple", "orange", "grape", "banana"};
// Lists
std::list<double>   xs {};
std::list<double>   ys{2.0, 3.0, 4.0, 5.0};   
// Sets 
std::set<double>    xs{};
std::set<double>    ys {3.0, 4.0, 3.0, 10.0};
// Unordered set 
std::unordered_set<int>    xs{};
std::unordered_set<string> zs {"apple", "orange", "grape", "banana"};

// Maps, aka hash map, aka dictionary, aka hash table 
std::map<std::string,double> prices {{"orange", 10.0}, {"grapes", 25.12}, {"apple", 4.12}};
  • Initialization of C-structus POD - Plain Old Data
struct Point3D{
    double x;
    double y;
    double z;
};

// Before C++11
//---------------------------
Point3D pa;
pa.x = 10.0;
pa.y = 20.0;
pa.z = 35.0;

// After C++11
//---------------------------
Point3D pb {10.0, 20.0, 35.0};
Point3D pc = {10.0, 20.0, 35.0};

// C++ Vector - way 1
std::vector<Point3D> {{ 20.0, 15.0, 5.0}, { 10.0, 25.0, 12.4}, {-14.0, 0.32, 51.43}};

// C++ Vector - way 2 
std::vector<Point3D> {
      Point3D{ 20.0, 15.0, 5.0}
     ,Point3D{ 10.0, 25.0, 12.4}
     ,Point3D{-14.0, 0.32, 51.43}
};

// C++ Map Containers 
std::map<std::string, Point3D> locations = {
     {"City 1",    { 20.0, 15.0, 5.0}}
    ,{"Somewhere", { 10.0, 25.0, 12.4}}
    ,{"Nowhere",   {-14.0, 0.32, 51.43}}     
};

// or in more pleasant notation 
// C++ Map Containers 
auto locations2 = std::map<std::string, Point3D> {
     {"City 1",    { 20.0, 15.0, 5.0}}
    ,{"Somewhere", { 10.0, 25.0, 12.4}}
    ,{"Nowhere",   {-14.0, 0.32, 51.43}}     
};

//--- Functions -------//

Point3D makeOriginPoint(){
    // return Point3D{0.0, 0.0, 0.0};
    return {0.0, 0.0, 0.0};
}

Classes:

class A{
public: 
    A(double x, double y, double z, std::string name):
        m_x(x),
        m_y(y),
        m_z(z),
        m_name(name) {      
    }
    auto getX() -> double{ return m_x;}
    auto getY() -> double {return m_y;}
    auto getZ() -> double {return m_z;} 
private:
    double m_x;
    double m_y;
    double m_z; 
};

void display(const A& a){
    std::cout << "A (" << a.getX() << " " << a.getY() << a.getZ() << ")" << "\n";
}

int main(){
    A instance1 {10.0, 20.0, 15.0, "unknown binary blob"};  
    display(instance1);
    display({10.0, 20.0, 15.0, "unknown binary blob"});
    return 0;
}

References:

1.11 Iterator based loops and for-range based loops

1.11.1 Iterator based loop

Loop over a vector:

std::vector<std::string> xs {"Hello", "world", "C++", "HPC", "awesome"};

for(std::vector<std::string>::iterator it = xs.begin(); it != xs.end(); ++it){
     std::cout << "word = '" << *it << "' size = " << it->size() << "\n";
}

/** Output: 
   word = 'Hello' size = 5
   word = 'world' size = 5
   word = 'C++' size = 3
   word = 'HPC' size = 3
   word = 'awesome' size = 7
   >> 
 */

// === OR =====//

for(auto it = xs.begin(); it != xs.end(); ++it){
     std::cout << "word = '" << *it << "' size = " << it->size() << "\n";
}

/** Output: 
        word = 'Hello' size = 5
        word = 'world' size = 5
        word = 'C++' size = 3
        word = 'HPC' size = 3
        word = 'awesome' size = 7
 */

Loop over a deque:

auto ds = std::deque<std::string> {"Physics", "C++", "STL", "Math", "Algebra", "Electronics"};

for(std::deque<std::string>::iterator it = ds.begin(); it != ds.end(); ++it){
      std::cout << "word = '" << *it << "' size = " << it->size() << "\n";
}

// Or: 
for(auto it = ds.begin(); it != ds.end(); ++it){
      std::cout << "word = '" << *it << "' size = " << it->size() << "\n";
}

/** Output: 
   word = 'Physics' size = 7
   word = 'C++' size = 3
   word = 'STL' size = 3
   word = 'Math' size = 4
   word = 'Algebra' size = 7
   word = 'Electronics' size = 11
 */

Loop over a list:

// Struct is a class with everything public 
struct Waypoint{
    std::string name;
    double      lat;
    double      lon;
    // This constructor is necessary for storing this object
    // in STL collections/containers 
    Waypoint(){}
    Waypoint(const std::string& name, double lat, double lon):
            name(name), lat(lat), lon(lon){}
};

>> auto wp = Waypoint("Paris", 48.23, 22.12)
>> wp.name
(std::string &) "Paris"
>> wp.lat
(double) 48.230000
>> wp.lon
(double) 22.120000
>> 
>> 

auto waypoints = std::list<Waypoint>{};
waypoints.emplace_back("Paris", 48.8566, 2.3522);
waypoints.emplace_back("Tokyo", 35.6895, 139.6917);
waypoints.emplace_back("Beijing", 39.9042, 116.4074);
waypoints.emplace_back("Cape Town", -33.9249, 18.4241);
waypoints.emplace_back("Buenos Aires", -34.6037, -58.3816);

std::cout << std::fixed << std::setprecision(2);

for(auto it = waypoints.begin(); it != waypoints.end(); ++it){
        std::cout << std::setw(15) << std::left  << it->name
                          << std::setw(15) << std::right << it->lat
                          << std::setw(15) << it->lon
                          << "\n";
}

Output:

Paris                    48.86           2.35
Tokyo                    35.69         139.69
Beijing                  39.90         116.41
Cape Town               -33.92          18.42
Buenos Aires            -34.60         -58.38

Loop over a map:

auto values = std::map<std::string, double>{
      {"x",  2.1023}
     ,{"y",  sqrt(2)}
     ,{"e",  exp(1)}
     ,{"pi", 3.1415}
     ,{"3/4", 3.0 / 4.0}
};

std::cout << std::fixed << std::setprecision(2);

for(std::map<std::string, double>::iterator it = values.begin(); it != values.end(); it++){
        std::cout << std::setw(5) << std::left  << it->first
                  << std::setw(5) << std::right << it->second
                  << "\n";
}

// OR:
for(auto it = values.begin(); it != values.end(); it++){
        std::cout << std::setw(5) << std::left  << it->first
                  << std::setw(5) << std::right << it->second
                  << "\n";
}

/** Output: 
   3/4   0.75
   e     2.72
   pi    3.14
   x     2.10
   y     1.41
 */

1.11.2 For-range based loops

C++11 and new standards allow to iterate over any STL container or class with methods (member functions) .begin() or .end() using a simpler and cleaner syntax than the old iterator loop syntax.

Loop over literals

#include <iostream>

// Possible since C++11
>> for(const auto& x: {10, 20, 40, 50, 60}) {
       std::cout << " x = " << x << "\n";
    }
 x = 10
 x = 20
 x = 40
 x = 50
 x = 60

>> for(const auto& word: {"C++", "C++11", "Real Time"}) {
       std::cout << " word = " << word << "\n";
    }
 word = C++
 word = C++11
 word = Real Time

 >> for(const std::string& word: {"C++", "C++11", "Real Time"}) {
        std::cout << " word = "+ word
                  << "  size = " << word.size()
                  << "\n";
    }
 word = C++  size = 3
 word = C++11  size = 5
 word = Real Time  size = 9

Loop over a vector:

std::vector<std::string> xs {"Hello", "world", "C++", "HPC", "awesome"};

// Use: const auto&  to avoid uncessary copies, use auto& if there is modification of x.
// The const keyword will generate a compiler error if there is 
// any attempt to modify the value of x. 
for(const auto& x: xs){
     std::cout << "word = '" << x << "' size = " << x.size() << "\n";
}

/** Output: 
   word = 'Hello' size = 5
   word = 'world' size = 5
   word = 'C++' size = 3
   word = 'HPC' size = 3
   word = 'awesome' size = 7
 *---------------*/

Loop over a list:

// Struct is a class with everything public 
struct Waypoint{
        std::string name;
        double      lat;
        double      lon;
        // This constructor is necessary for storing this object
        // in STL collections/containers 
        Waypoint(){}
        Waypoint(const std::string& name, double lat, double lon):
                name(name), lat(lat), lon(lon){}
};

auto waypoints = std::list<Waypoint>{
       {"Paris", 48.8566, 2.3522}
      ,{"Tokyo", 35.6895, 139.6917}
      ,{"Beijing", 39.9042, 116.4074}
      ,{"Cape Town", -33.9249, 18.4241}
      ,{"Buenos Aires", -34.6037, -58.3816}};


std::cout << std::fixed << std::setprecision(2);

for(const auto& wp: waypoints){
        std::cout << std::setw(15) << std::left  << wp.name
                          << std::setw(15) << std::right << wp.lat
                          << std::setw(15) << wp.lon 
                          << "\n";
}

Ouput:

Paris                    48.86           2.35
Tokyo                    35.69         139.69
Beijing                  39.90         116.41
Cape Town               -33.92          18.42
Buenos Aires            -34.60         -58.38

Loop over a map

auto values = std::map<std::string, double>{
      {"x",  2.1023}
     ,{"y",  sqrt(2)}
     ,{"e",  exp(1)}
     ,{"pi", 3.1415}
     ,{"3/4", 3.0 / 4.0}
};

std::cout << std::fixed << std::setprecision(3);

for(const auto& p: values){
        std::cout << std::setw(5) << std::left  << p.first
                  << std::setw(5) << std::right << p.second
                  << "\n";
}

/** Output: 
   3/4  0.750
   e    2.718
   pi   3.142
   x    2.102
   y    1.414
 *==============*/

1.12 C++11 - Scoped Enum

Scoped enumerations are a more type-safe alternative to the old C-enums as it has several problems realated to namespace conflicts and implicit conversions that can introduce bugs hard to catch and reason about.

Syntax:

  • Simple scoped enum class
enum class Color {
   white,
   black,
   yellow,
   red,
   blue,
};

>> Color::white
(Color) (Color::white) : (int) 0
>> Color::blue
(Color) (Color::blue) : (int) 4
>> Color::yellow
(Color) (Color::yellow) : (int) 2
>> Color::red
(Color) (Color::red) : (int) 3
>> 

>> static_cast<int>(Color::blue)
(int) 4
>> static_cast<int>(Color::white)
(int) 0
>> static_cast<int>(Color::yellow)
(int) 2
>> 
  • Scoped enum with hexadecimal error codes.
enum class ErrorCode: std::uint32_t {
   tankNotFilled = 0xff,
   missingSupply = 0x2f,
   lowBattery    = 0x2a,
   unknown       = 0x24                                         
};
  • Scoped enums with chars
enum class ErrorCodeLetter: char {
   tankNotFilled = 'x',
   missingSupply = 'y',
   lowBattery    = 'a',
   unknown       = 'k'                                         
};

>> ErrorCodeLetter::tankNotFilled
(ErrorCodeLetter) (ErrorCodeLetter::tankNotFilled) : (char) 120
>> ErrorCodeLetter::missingSupply
(ErrorCodeLetter) (ErrorCodeLetter::missingSupply) : (char) 121
>> ErrorCodeLetter::unknown
(ErrorCodeLetter) (ErrorCodeLetter::unknown) : (char) 107
>> static_cast<char>(ErrorCodeLetter::unknown)
(char) 'k'
>> static_cast<char>(ErrorCodeLetter::missingSupply)
(char) 'y'
>> 

More Exhaustive Example:

enum class MachineStatus: std::uint32_t {
   running = 0xf5,
   iddle   = 0x2a,
   waiting = 0x35,
   failure = 0x24                                         
};

Example: CERN's root shell.

>> MachineStatus::running
(MachineStatus) (MachineStatus::running) : (unsigned int) 245
>> MachineStatus::iddle
(MachineStatus) (MachineStatus::iddle) : (unsigned int) 42
>> MachineStatus::waiting
(MachineStatus) (MachineStatus::waiting) : (unsigned int) 53
>> MachineStatus::failure
(MachineStatus) (MachineStatus::failure) : (unsigned int) 36
>> 

>> auto status = MachineStatus::running
(MachineStatus) (MachineStatus::running) : (unsigned int) 245

>> std::cout << "Machine status = " << std::hex << "0x" << static_cast<std::uint32_t>(status) << std::endl;
Machine status = 0xf5
>> 
>> 

>> status = MachineStatus::iddle 
(MachineStatus) (MachineStatus::iddle) : (unsigned int) 42
>> std::cout << "Machine status = " << std::hex << "0x" << static_cast<std::uint32_t>(status) << std::endl;
Machine status = 0x2a
>> 

>> if(status == MachineStatus::iddle) { std::cout << "Machine is iddle" << std::endl; }
Machine is iddle
>> 

>> status = MachineStatus::running
(MachineStatus) (MachineStatus::running) : (unsigned int) 245
>> if(status == MachineStatus::running) { std::cout << "Machine is running" << std::endl; }
Machine is running
>> 

>> static_cast<std::uint32_t>(MachineStatus::running)
(unsigned int) 245
>> static_cast<std::uint32_t>(MachineStatus::iddle)
(unsigned int) 42
>> static_cast<std::uint32_t>(MachineStatus::iddle) == 0x2a
(bool) true
>> 
>> static_cast<MachineStatus>(0x2a) 
(MachineStatus) (MachineStatus::iddle) : (unsigned int) 42
>> 
>> static_cast<MachineStatus>(0x2a) == MachineStatus::iddle
(bool) true
>> 

1.13 OOP Simple Class

#include <iostream>
using namespace std;

class Date
{
public:
  int year, month, day;

  // ---- Public Class Members ----- //

  void showDate();
  void showDate2();
  int  getYear();
  int  getDay();
  int  getMonth();
};


void Date::showDate(){
  cout << "Date = " << this->year << "-" << this->month << "-" << this->day << endl;
}

void Date::showDate2(){
  cout << "Date = " << year << "-" << month << "-" << day << endl;
}

int Date::getYear(){
  return year;
}

int Date::getMonth(){
  return month;
}

int Date::getDay(){
  return day;
}

Date makeDate (int y, int m, int d){
  Date date;
  date.year  = y ;
  date.month = m ;
  date.day   = d ;
  return date;
}

void printDate(Date d){
  cout << "Date is " << d.year << "-" << d.month << "-" << d.day << endl;
}


int main(){
  Date d;
  d.day   = 10;
  d.month = 4;
  d.year  = 1998;

  cout << "Date (YMD) is = " << d.year << "-" << d.month << "-" << d.day << endl;
  d.showDate();
  d.showDate2();
  printDate(d);

  cout << "Year of date d is  = " << d.getYear() << endl;
  cout << "Month of date d is = " << d.getMonth() << endl;

  printDate(makeDate(1996, 8, 20));

  return 0;
}

Running:

$ g++ cppClasses1.cpp -o cppClasses1.bin&& ./cppClasses1.bin
Date (YMD) is = 1998-4-10
Date = 1998-4-10
Date = 1998-4-10
Date is 1998-4-10
Year of date d is  = 1998
Month of date d is = 4
Date is 1996-8-20

1.14 Conversion Constructor X Explicit Constructor - explicit keyword

A converting constructor (aka conversion constructor or implicit constructor) is any constructor member function callable with a single argument not annotated with the explicit keyword. A converting constructor allows implicit initialization by assignment or when passing a parameter to a function.

Example:

class TestClass{
public:
    // Constructor 1: Conversion constructor 
    TestClass(int x)
       : m_x(x)
    {
         std::printf(" [LOG] Intialize m_x = %d\n", x);
    }
    // Constructor 2: Conversion constructor 
    TestClass(char ch)
       : m_ch(ch)
    {
         std::printf(" [LOG] Intialize m_ch = %c\n", ch);
    }
    // Constructor 3: Not conversion constructor / explicit constructor 
    explicit TestClass(const std::string& text)
       : m_text(text)
    {
         std::printf(" [LOG] Initialize m_text = %s\n", text.c_str());
    }
    // Constructor 4: 
    TestClass(int x, char ch)
    {
         std::printf("[LOG] Intialize m_x = %d - m_ch = %c\n", x, ch);
    }
    void show(){
         std::printf("[LOG] TestClass{ m_x = %d ; m_ch = '%c' ; m_text = '%s' }\n"
         ,m_x, m_ch, m_text.c_str());
    }
    // --- Member variables ---- // 
    int        m_x;
    char       m_ch;
    std::string m_text;
};

Example:

  • The constructors 1 and 2 not annotated with explicit allows implicit initialization by assignment. While this behavior can be useful, it can lead unintended type conversions. In order to avoid the implicit conversion, it is necessary to annotate the member function with explicit. After this modification, any attempt to implicit conversion will yield a compilation error.

Testing conversion constructors:

>> TestClass cls1 = 100;
 [LOG] Intialize m_x = 100
>> 
>> TestClass cls2 = 'k';
 [LOG] Intialize m_ch = k

// Show contents of cls1 and cls2 
>> cls1.show()
[LOG] TestClass{ m_x = 100 ; m_ch = '' ; m_text = '' }
>> 

>> cls2.show()
[LOG] TestClass{ m_x = 0 ; m_ch = 'k' ; m_text = '' }

>> cls1 = 200;
 [LOG] Intialize m_x = 200
>> cls1 = 400;
 [LOG] Intialize m_x = 400
>> 
>> cls1.show()
[LOG] TestClass{ m_x = 400 ; m_ch = '�' ; m_text = '' }
>> 

Testing explicit constructor (constructor 3 annotated with explicit):

  • It will result in a compiation error.
// Compilation error - because constructor was annotated with explicit.
>> TestClass cls3 = std::string("binary data");
ROOT_prompt_5:1:11: error: no viable conversion from 'std::string' (aka 'basic_string<char>') to
      'TestClass'
TestClass cls3 = std::string("binary data");
          ^      ~~~~~~~~~~~~~~~~~~~~~~~~~~
input_line_15:1:7: note: candidate constructor (the implicit copy constructor) not viable: no known
      conversion from 'std::string' (aka 'basic_string<char>') to
      'const TestClass &' for 1st argument

A constructor annoatated with explicit can only be initialized in the following ways:

>> TestClass cls3b(std::string("binary data"));
 [LOG] Initialize m_text = binary data

>> TestClass cls3c("binary data");
 [LOG] Initialize m_text = binary data

>> auto cls3d = TestClass("binary data");
 [LOG] Initialize m_text = binary data

>> auto cls3e = TestClass{"binary data"};
 [LOG] Initialize m_text = binary data
>> 

Functions and implicit constructors:

void clientCode(const TestClass& cls){
    std::printf(" ==> TestClass { m_x = %d ; m_ch = %c ; m_text = %s }\n",
    cls.m_x, cls.m_ch, cls.m_text.c_str());
}

>> clientCode(10)
 [LOG] Intialize m_x = 10
 ==> TestClass { m_x = 10 ; m_ch = � ; m_text =  }
>> 

>> clientCode('x')
 [LOG] Intialize m_ch = x
 ==> TestClass { m_x = 305009792 ; m_ch = x ; m_text =  }
>> 

//===========>  Implicit Conversion doesn't happen! ==============//  

// std::string cannot be implicit converted to TestClass since the 
// constructor is annoatated with 'explicit'. 
>> clientCode(std::string("helo world"));
ROOT_prompt_52:1:1: error: no matching function for call to 'clientCode'
clientCode(std::string("helo world"));
^~~~~~~~~~
ROOT_prompt_45:1:6: note: candidate function not viable: no known conversion from 'std::string' (aka
      'basic_string<char>') to 'const TestClass' for 1st argument
void clientCode(const TestClass& cls){

// In this case, the only way to perform the operation is by calling
// the constructor explicitly.
>> clientCode(TestClass("helo world"));
 [LOG] Initialize m_text = helo world
 ==> TestClass{ m_x = 876161456 ; m_ch = '�' ; m_text = 'helo world' }

Further reading:

1.15 OOP Value Semantics X Reference Semantics

According to the - ISO C++, value and reference semantics are defined as:

With reference semantics, assignment is a pointer-copy (i.e., a reference). Value (or “copy”) semantics mean assignment copies the value, not just the pointer. C++ gives you the choice: use the assignment operator to copy the value (copy/value semantics), or use a pointer-copy to copy a pointer (reference semantics). C++ allows you to override the assignment operator to do anything your heart desires, however the default (and most common) choice is to copy the value.

Definitions:

Reference Semantics: Behavior where composite types are passed by reference when assigned; passed as function or method parameters or returned from functions. This is the default behavior of most object oriented programming languages, except C++.

In Java, C#, Scala, Python and etc. Objects have reference semantics by default. This example in Scala programming language shows how reference semantics works in most languages.

class Foo(name: String){
  private var _name = name
  def setName(name: String) =
    _name = name
  def getName() =
    name
  override def toString() =
    s"Foo { name = $name }"
}

scala> var x = 10
x: Int = 10

// Primitive types have value semantics: assignment of variables of
// primitive types, creates a copy, so both variables can be modified
// without changing each other.
scala> var y = x
y: Int = 10

// By modifying x, the value of y remains the same.
scala> x = 25
x: Int = 25

scala> y
res5: Int = 10

scala> 

//===> Composite and complexity types have reference semantics by default 
// in languages other than C++.

scala> val foo = new Foo("bar")
foo: Foo = Foo { name = bar }

// Assignment doesn't create a copy like assignment 
// of primitive type, actually the assignment creates 
// a reference to the object foo. As result, modifying 
// one of the objects, modifies the other.
scala> val bar = foo
bar: Foo = Foo { name = bar }

// Modifying bar modifes foo. 
scala> bar.setName("something")

scala> bar
res3: Foo = Foo { name = something }

scala> foo
res4: Foo = Foo { name = something }

// Passing as function parameter doesn't create a copy like in C++, 
// it passes the object by reference, so if the parameter is modified inside
// the function, the original object will be modified too. 
 def setFooPrint(param: Foo){
   param.setName("dummy name")
   println(foo)
 }

 scala> setFooPrint(foo)
 Foo { name = dummy name }

 scala> foo
 res8: Foo = Foo { name = dummy name }

 // Returning an object from a function doesn't create a copy as would happen 
 // with primitive types.
 def modifyReturn(param: Foo, newName: String) = {
   param.setName(newName)
   param
 }
 scala> val foob = modifyReturn(foo, "Scala + C++ + JNI == HPC")
 foob: Foo = Foo { name = Scala + C++ + JNI == HPC }

 scala> foo
 res9: Foo = Foo { name = Scala + C++ + JNI == HPC }

Value Semantics: Behavior where composite types such as instances of classes are treated as primitive type such as booleans, integers or float point numbers. In the value semantics, a copy is created when variables are assigned; passed as parameters to functions or methods and returned from functions. So modifying one of the variables doesn't change the other.

Unlike other languages, C++ uses value semantics by default, it means that in operations such as assignment; returning objects from functions and passing objects as parameters create a full copy of the object, instead of creating a reference to the object as would happen in most object oriented programming languages such as Java, C#, Python, Ruby and etc. C++ also supports reference semantics, however it is not the default behavior and unlike in the majority of programming languages, requires explicit annotation to pass objects by reference or create a reference to the object.

Value Semantics in C++

Example: demonstration of value semantics in C++ tested in the CERN's C++ ROOT REPL:

  • Note: this code can be copied and pasted in the CERN's ROOT REPL.
#include <iostream>
#include <string>

class Foo{
private:
  std::string _name;
public:
  // Constructor 
  Foo(std::string name):_name(name){}
  // Copy constructor
  //--------------------------
  // Note: If it is not defined, the compiler, defines 
  // a default copy constructor. It was created to demonstrate
  // when the copy constructor is invoked.
  Foo(const Foo& rhs){
    _name = rhs._name;    
    std::cout << " [INFO] Copy constructor invoked." << std::endl;    
  }
  // Copy assignment-operator
  //--------------------------
  // Note: It is similar to the copy constructor and
  // default assignment copy-assignment-operator is created
  // by the compiler if the user doesn't define it.
  Foo operator= (const Foo& rhs){    
    std::cout << " [INFO] Copy-assignment operator invoked." << std::endl;
    return Foo(rhs._name);
  }
  void setName(std::string name){
    _name = name;
  }
  std::string getName() const {
    return _name;
  }
  void show(){
    std::cout << "Foo { name = " << _name << " } " << std::endl;
  }
  void show2() const {
    std::cout << "Foo { name = " << _name << " } " << std::endl;
  }
};

Assignment creates a copy, unlike in most OOP languages like Java, C#, Python and so on.

>> Foo foo("foo");
>> foo.show()
Foo { name = foo } 

// Assingment creates a copy, unlike in most OO languages
>> Foo bar = foo; 
 [INFO] Copy constructor invoked.

// Modifying one of the objects, doens't change the other. 
>> bar.show()
Foo { name = foo } 

>> bar.setName("I am object bar")

>> bar.show()
Foo { name = I am object bar } 

>> foo.show()
Foo { name = foo } 
>> 

// foo and bar objects aren't the same as they have 
// different memory locations. 
>> &foo == &bar
(bool) false
>>

Primitive and composite types are passed by value in C++, unlike in most OOP languages. So, it means that a copy of the object is created.

void setFooPrint(Foo param, std::string name){
  param.setName(name);
  param.show();
  std::cout << " name = " << param.getName() << std::endl;
}

// Modifying the function paramenter, doesn't modify the passed object.
>> setFooPrint(foo, "dummy name")
 [INFO] Copy constructor invoked.
Foo { name = dummy name } 
 name = dummy name

>> foo.show()
Foo { name = foo } 
>> 

Returning an object from function, creates a copy of the object instead of returning a reference to it like in Java, Scala, Python and most languages.

Foo modifyReturn(Foo param, std::string newName) {
  param.setName(newName);
  return param;
}

>> auto ret = modifyReturn(foo, "New name")
 [INFO] Copy constructor invoked.
 [INFO] Copy constructor invoked.
(Foo &) @0x7f54f0288050

>> &ret == &foo
(bool) false

>> ret.show()
Foo { name = New name } 

>> foo.show()
Foo { name = foo } 
>> 

Value semantics and STL

  • Objects can be stored in STL containers by value, reference or by pointers.
#include <deque> // Double ended queue collection 

>> std::deque<Foo> xs;

// Temporary objects are created on the stack, 
// copied to the deque data structure and then 
// put on the collection. 
// 
>> xs.push_back(Foo("hello"));
 [INFO] Copy constructor invoked.
>> xs.push_back(Foo("world"));
 [INFO] Copy constructor invoked.
>> xs.push_back(Foo("value"));
 [INFO] Copy constructor invoked.
>> xs.push_back(Foo("semantics"));
 [INFO] Copy constructor invoked.
>> 

>> xs
(std::deque<Foo> &) { @0x1393820, @0x1393840, @0x1393860, @0x1393880 }
>> 

>> xs.size()
(unsigned long) 4
>> 

>> xs.at(0).show()
Foo { name = hello } 
>> xs.at(0).show2()
Foo { name = hello } 
>> 
>> xs.at(2).show()
Foo { name = value } 
>> 

// Error: invoke const reference method which is not annotated with 'const'
>> for(const auto& x: xs) { x.show(); }
ROOT_prompt_56:1:26: error: member function 'show' not viable: 
'this' argument has type 'const Foo', but function is not marked const
for(const auto& x: xs) { x.show(); }

// Works as show2() is annotated with 'const'
>> for(const auto& x: xs) { x.show2(); }
Foo { name = hello } 
Foo { name = world } 
Foo { name = value } 
Foo { name = semantics } 
>> 

// Watesful for-loop: performs uncessary copies which could slow down and inccur 
// on a significant performance overhead in case of a large object.
>> for(auto x: xs) { x.show(); }
 [INFO] Copy constructor invoked.
Foo { name = hello } 
 [INFO] Copy constructor invoked.
Foo { name = world } 
 [INFO] Copy constructor invoked.
Foo { name = value } 
 [INFO] Copy constructor invoked.
Foo { name = semantics } 
>> 

// By using emplace_back - a copy is not created.
>> auto xs2 = deque<Foo>()
(std::deque<Foo, std::allocator<Foo> > &) {}
>> 
>> xs2.emplace_back("hello")
>> xs2.emplace_back("world")
>> xs2.emplace_back("value")
>> xs2.emplace_back("semantics")
>> xs2
(std::deque<Foo, std::allocator<Foo> > &) { @0x411ff30, @0x411ff50, @0x411ff70, @0x411ff90 }
>> 

>> for(const auto& x: xs2) { x.show2(); }
Foo { name = hello } 
Foo { name = world } 
Foo { name = value } 
Foo { name = semantics } 
>> 

Reference Semantics in C++

Unlike in most programming languages where reference semantics for complex types such as object is the default behavior, in C++ reference semantics requires explicit annotation with reference operator (&) or passing objects by pointer.

The default behavior of passing by value cause significant memory and performance overhead. In order to avoid unnecessary copies, it is preferable to pass objects by reference with operator (&) or by const reference when the object is not supposed to be modified by the function the objects are passed to.

  • Create a reference in assignment operation instead of a copy.
>> foo.show()
Foo { name = foo } 

>> Foo& ref1 = foo;

>> ref1.setName("I am foo reference")
>> foo.show()
Foo { name = I am foo reference } 
>> 

// The reference has the same memory location of foo.
>> &foo == &ref1
(bool) true
>> 
  • Passing a parameter by reference instead of passing it by value. Note: that the copy constructor is not invoked when passing by reference.
void setFooPrintRef(Foo& param, const std::string& name){
  param.setName(name);
  param.show();
  std::cout << " name = " << param.getName() << std::endl;
}

>> setFooPrintRef(foo, "dummy name")
Foo { name = dummy name } 
 name = dummy name

>> foo.show()
Foo { name = dummy name } 
>> 
  • Returning objects from functions as references.
Foo& modifyReturnRef(Foo& param, std::string newName) {
  param.setName(newName);
  return param;
}

>> foo.setName("unnamed")

>> fooRefx.setName("I am foo reference")
>> foo.show()
Foo { name = I am foo reference } 

>> &foo == &fooRefx
(bool) true
>> 

>> auto& fooRefAuto = modifyReturnRef(foo, "C++ type inference auto!")
(Foo &) @0x7f54f0288010

>> foo.show()
Foo { name = C++ type inference auto! } 

>> fooRefAuto.show()
Foo { name = C++ type inference auto! } 

>> fooRefAuto.setName("C++17")

>> foo.show()
Foo { name = C++17 } 
>> 

>> &foo == &fooRefAuto
(bool) true
>> 
  • Const references cannot be modified as any attempt to change it will result in a compile-time error.
>> Foo foo("foo");

>> foo.show()
Foo { name = foo } 

>> foo.getName()
(std::string) "foo"

>> const Foo& fooRefConst = modifyReturnRef(foo, "C++ constant ref.")
(const Foo &) @0x7fbf2003c010

>> foo.show()
Foo { name = C++ constant ref. } 

>> foo.getName()
(std::string) "C++ constant ref."
>> 

>> fooRefConst.show2()
Foo { name = C++ constant ref. } 
>> 

// Any attempt to call a method not annotated with const will result 
// in a compile-time error. 
>> fooRefConst.show()
ROOT_prompt_52:1:1: error: member function 'show' not viable: 'this' 
argument has type 'const Foo', but function is not marked const
fooRefConst.show()
^~~~~~~~~~~
ROOT_prompt_30:1:6: note: 'show' declared here
void show(){ 

Summary

  • Value Semantics X Reference Semantics
    • Value Semantics -> Objects are assigned, passed to functions and return from functions as primitive types without being modified as what is modified is a copy of the object. This is default behavior of C++.
      • Object A = B; => (C++ Only) Creates object A as a copy of the object B.
      • Object A = B.copy() (C#, Java, Python …) Creates object A as copy of object B. As value-semantics is not the default behavior in thoses languages, it is necessary to invoke some deep copy method explicity.
    • Reference Semantics -> Objects are passed by reference or pointer; assigned by pointer and so on. Objects passed to functions using reference semantics can modified. This is the default behavior of Java, Python, C# and other programming languages.
      • Object A = B; (C#, Java, Python …) => The object A is reference to object B. Any modification to A or B will modify both as the refer to the same memory location.
      • Object& A = B; (C++ only) => Creating a reference in C++ requires an explicit annotation with operator (&) as it is non-default behavior.
  • Most programming languages, except C++, use value-semantics for primitive types and reference semantics for complex or composite types such as classes due to performance reasons and avoid uncessary copies.
  • C++ uses values-semantics by default for all types, unlike most programming languages, when any primitive type or composite type such as class when assigned, passed to functions or returned from functions, a copy is created and the original object is not changed.
  • C++ supports both value and reference semantics which is not default for objects linke in Java, Python and other languages. The reference semantics requires explicit annotation.
  • In order to avoid unncessary copies that can lead memory or peformance overhead, it is preferable to use reference semantics. In other words, large objects should be passed by reference, const reference or pointer to functions or methods.
  • Move semantics optimizes return-by value avoiding unnecessary copies. The copy overhead that happens when returning an object from a function can be avoided by defining a move constructor (see C++11's move semantics) which transfer resource ownership from the object defined locally within the function body to the returned object.

Further Reading:

1.16 TODO Memory Allocation - Stack X Heap

1.16.1 Program/Process memory

The memory of a process or running program (compile object-code) can be divided into the segments:

  • Code (text segment):
    • Contains the program compiled as object-code. Generally, this segment is read-only.
  • Bss:
    • Contains non-initialized global variables.
  • Data:
    • Contains intialized global variables.
  • Stack (aka Call-stack) or stack memory
    • Contains function arguments and current function's local variables.
    • In C++, variables, objects and arrays are allocated on the stack by default.
    • Variables: allocated at compile-time.
    • Stack-allocated variables are also called automatic variables.
    • Limit: Stack is limited, on Linux Kernel 8 kb (kbytes). Large objects should not be allocated on the stack.
    • Memory management: Variables are automatically deleted when out of scope.
  • Heap (dynamic memory or free-store)
    • Dynamically allocated variables or objects (variables allocated with C++ new operator or C-malloc) are instatiated on the heap.
    • Example - heap-allocated array: int* ptr = new int [10];
    • Variables: allocated at runtime.
    • Limit: The limit is the RAM memory or the virtual memory.
    • Memory management: Manual (C++ is not garbage collected), for every statement with new operator, there must be a matching delete operator releasing the allocated memory in order to avoid memory leak.
    • Only accessible through pointers.

Further reading:

1.16.2 stack Allocation - Automatic Variables

  1. Overview

    In C++, variables, objects or arrays defined inside functions are allocated on the stack by default. The memory allocated is automatically released when they go out scope, thus there is no need to worry about allocation in this case.

    Stack allocation features:

    • Variables allocated on the stack are also called automatic variables.
    • The array size cannot be changed at runtime and it cannot grow or shrink. Therefore, it is not possible to allocate an array in this way:
      • int n = 10; double array[n];
    • The stack has limited size, the Linux Kernel has an 8 Mbs stack, as a result, large objects cannot be allocated in the stack. So, if one tries to allocate an array: int array[1000000000], it will cause segmentation fault.
      • TL;DR: Large objects should be allocated on the heap or dynamic memory.
    • Primitive type variable, objects and arrays are automatically released (aka deleted) when they go out of scope. In the case of objects, their dectructors methods or member functions are always called.
  2. Example: Stack-allocated arrays

    Try to define/allocate a variable-size array.

    >> int k = 4;
    
    // Error: It is impossible to allocate variable-size array on stack or
    // static memory (as global variable).
    >> double arr_flt[k];
    ROOT_prompt_38:1:8: error: variable length array 
    declaration not allowed at file scope
    
    

    Defining an stack-allocated array with size set by const variable. In C++, it is preferable to use const variable instead of preprocessor macros for defining consts.

    // DO NOT: #define arra_size 3 
    >> const size_t arr_size = 3;
    >> double arr_flt[arr_size]
    (double [3]) { 0.0000000, 0.0000000, 0.0000000 }
    >> 
    

    Defining an stack-allocated array, assuming that all statements are executed inside a function.

    // Define array 
    >> int arr1[5];
    
    >> arr1
    (int [5]) { 0, 0, 0, 0, 0 }
    >> 
    
    // Set elements 
    >> arr1[0] = 10, arr1[1] = 5; arr1[2] = 6, arr1[3] = 9, arr1[4] = 15;
    >> 
    
    >> arr1
    (int [5]) { 10, 5, 6, 9, 15 }
    >> 
    
    // Query elements 
    >> arr1[0]
    (int) 10
    >> arr1[2]
    (int) 6
    >> arr1[3]
    (int) 9
    >> 
    

    Loop over array:

    >> for(int i = 0; i < 5; i++){ std::cout << " " << arr1[i] << "\n"; }
     10
     5
     6
     9
     15
    >> 
    

    Array as a pointer:

    • The array arr1 is actually a pointer to its first element. This array is a memory block of five consecutive allocated integers with the same size.
    // The array is actually a pointer to array[0] or the 0-th 
    // first element of memory block.
    //-------------------------------------------------
    >> *arr1
    (int) 10
    // Second memory block 
    >> *(arr1 + 1)
    (int) 5
    // Third memory block 
    >> *(arr1 + 2)
    (int) 6
    >> *(arr1 + 3)
    (int) 9
    >> *(arr1 + 4)
    (int) 15
    >>
    
    >> *(&arr1[0] + 0)
    (int) 10
    >> *(&arr1[0] + 1)
    (int) 5
    >> *(&arr1[0] + 2)
    (int) 6
    >> *(&arr1[0] + 3)
    (int) 9
    >> *(&arr1[0] + 4)
    (int) 15
    >> 
    

    Many ways to manipulate the third element (index 2):

    >> *(&arr1[0] + 2)
    (int) 6
    >> *(&arr1[0] + 2) = 25
    (int) 25
    >> *(&arr1[0] + 2)
    (int) 25
    >> arr1[2]
    (int) 25
    >> 
    

    Passing arrays to functions:

    • An array (C-arrays) is not aware of its size, thus this parameter must be passed to functions alongside the array or pointer to first element.
    void printArray(size_t size, int xs []){
      for(size_t i = 0; i < size; i++)
        std::cout << " => xs[" << i << "] = " << xs[i] << "\n";
    }
    
    void printArray2(size_t size, int* xs){
      for(size_t i = 0; i < size; i++)
        std::cout << " => xs[" << i << "] = " << xs[i] << "\n";
    }
    
    void printArray3(size_t size, int* xs){
      for(size_t i = 0; i < size; i++)
        std::cout << " => xs[" << i << "] = " << *(xs + i) << "\n";
    }
    
    >> printArray(5, arr1)
     => xs[0] = 10
     => xs[1] = 5
     => xs[2] = 25
     => xs[3] = 9
     => xs[4] = 15
    >> 
    
    >> printArray2(5, arr1)
     => xs[0] = 10
     => xs[1] = 5
     => xs[2] = 25
     => xs[3] = 9
     => xs[4] = 15
    
    >> printArray2(3, arr1)
     => xs[0] = 10
     => xs[1] = 5
     => xs[2] = 25
    >> 
    
    
  3. Example: Stack-allocated objects

    Example: Stack-allocated object.

    #include <iostream>
    #include <string>
    
    class SomeClass{
       // Internal state 
       std::string _id; 
    public:
       // Default constructor is created by the compiler
       // if not defined. 
       SomeClass()
         : _id("unnamed") {}
    
       SomeClass(const std::string& id)
          : _id(id) 
       {
          std::cout << "[LOG] Object created = <" << _id << "> \n"; 
       }
       std::string getID(){ 
          return _id;  
       }
       void setID(const std::string& id){ 
          _id = id;  
       }
       // Copy-constructor is created by default by the compiler,
       SomeClass(const SomeClass& rhs){
         std::cout << "[LOG] Copy-constructo Object copyed = <" << rhs._id << ">\n";
         this->_id = rhs._id;
       }
       // Copy-assignment operator -
       SomeClass& operator=(const SomeClass& rhs){
         std::cout << "[LOG] Copy-assign operator Object copyed = <" << rhs._id << ">\n";
         this->_id = rhs._id;
         return *this;
       }
    
       // Destructor - is always generated by compiler
       // if not specified. 
       ~SomeClass()
       {
          std::cout << "[LOG] I was deleted <" << _id << "> " << "\n";
       }
    };
    

    Object - Allocated on stack or static memory

    Any object allocated on the stack (locally on the function) such as the variable cls is automatically deleted when it goes out of scope.

    // Return reference.
    SomeClass& Function(){  
       SomeClass cls("object-1");
       std::cout << "Object id = " << cls.getID() << "\n";
       return cls;
    }
    

    Testing:

    >> SomeClass& clsref = Function()
    
    [LOG] Object created = <object-1> 
    Object id = object-1
    [LOG] I was deleted <object-1> 
    (SomeClass &) @0x7ffcbbe2eed0
    
    // DO NOT DO IT! Reference to deleted object
    >> clsref.getID()
    
     *** Break *** segmentation violation
    

    Stack-allocated array of objects

    • An array of objects is always intialized with default-constructor
      >> SomeClass arr1[3];
      [LOG] Copy-constructo Object copyed = <unnamed>
      [LOG] Copy-constructo Object copyed = <unnamed>
      [LOG] Copy-constructo Object copyed = <unnamed>
      >> 
    
    >> arr[0].getID()
    (std::string) "unnamed"
    >> arr[1].getID()
    (std::string) "unnamed"
    >> arr[2].getID()
    (std::string) "unnamed"
    >> 
    >> 
    
    
    • Intialized array
    // Intialized array 
    >> SomeClass arr2[3] = { SomeClass("obj1"), SomeClass("obj2"), SomeClass("obj3")};
    [LOG] Object created = <obj1> 
    [LOG] Object created = <obj2> 
    [LOG] Object created = <obj3> 
    [LOG] Copy-constructo Object copyed = <obj1>
    [LOG] Copy-constructo Object copyed = <obj2>
    [LOG] Copy-constructo Object copyed = <obj3>
    
    >> arr2[0].getID()
    (std::string) "obj1"
    >> arr2[1].getID()
    (std::string) "obj2"
    >> arr2[2].getID()
    (std::string) "obj3"
    >> 
    
    // Uniform initialization 
    >> SomeClass arr3[3] = { {"objA"}, {"objB"}, {"objC"}};
    >> SomeClass arr3[3] = { {"objA"}, {"objB"}, {"objC"}};
    [LOG] Object created = <objA> 
    [LOG] Object created = <objB> 
    [LOG] Object created = <objC> 
    [LOG] Copy-constructo Object copyed = <objA>
    [LOG] Copy-constructo Object copyed = <objB>
    [LOG] Copy-constructo Object copyed = <objC>
    >> 
    
    • Manipulating objects in array:
    >> arr1[0].setID("O1"), arr1[1].setID("O2"), arr1[2].setID("O3");
    >> arr1[0].getID()
    (std::string) "O1"
    >> arr1[1].getID()
    (std::string) "O2"
    >> arr1[2].getID()
    (std::string) "O3"
    >>
    
    • Loop over array elements:
    void showArray(size_t n, SomeClass xs []){
      for(int i = 0; i < n; i++)
        std::cout << "obj[" << i << "] = " << xs[i].getID() << ", ";
      std::cout << "\n";
      std::cout.flush();
    }
    
    >> showArray(3, arr1)
    obj[0] = O1, obj[1] = O2, obj[2] = O3, 
    
    >> showArray(3, arr2)
    obj[0] = obj1, obj[1] = obj2, obj[2] = obj3, 
    
    >> showArray(3, arr3)
    obj[0] = objA, obj[1] = objB, obj[2] = objC, 
    >> 
    
    • Assign array elements:
    >> showArray(3, arr2)
    obj[0] = obj1, obj[1] = obj2, obj[2] = obj3, 
    >> 
    
    >> arr2[0] = SomeClass("1st-object");
    [LOG] Object created = <1st-object> 
    [LOG] Copy-assign operator Object copyed = <1st-object>
    [LOG] I was deleted <1st-object> 
    
    >> arr2[0].getID()
    (std::string) "1st-object"
    
    >> arr2[1] =  {"2nd-element"}; // Uniform initialization
    [LOG] Object created = <2nd-element> 
    [LOG] Copy-assign operator Object copyed = <2nd-element>
    [LOG] I was deleted <2nd-element> 
    
    >> arr2[1].getID()
    (std::string) "2nd-element"
    >> 
    
    >> showArray(3, arr2)
    obj[0] = 1st-object, obj[1] = 2nd-element, obj[2] = obj3, 
    
    • Defining an array inside a function:
    void exampleFunction(){
      const size_t n = 4;
      // Deleted when out of scope.
      SomeClass xss [n] = {{"C++98"}, {"C++11"}, {"C++17"}, {"C++20"}} ;
      showArray(n, xss);
    }
    
    >> exampleFunction()
    [LOG] Object created = <C++98> 
    [LOG] Object created = <C++11> 
    [LOG] Object created = <C++17> 
    [LOG] Object created = <C++20> 
    obj[0] = C++98, obj[1] = C++11, obj[2] = C++17, obj[3] = C++20, 
    [LOG] I was deleted <C++20> 
    [LOG] I was deleted <C++17> 
    [LOG] I was deleted <C++11> 
    [LOG] I was deleted <C++98> 
    

1.16.3 Dynamic/Heap Allocation

  1. Overview

    When dynamic allocation is needed

    Situation where dynamic allocation is needed:

    • The number of elements to be allocated is not known in advance.
    • The array or data structure needs to allocate more memory on demand.
    • Return an instance of a polymorphic class from a function. (Factory function.)
    • Type erasure: any object allocated on the heap can have its pointer casted to void* and then casted back to the object type.

    Note:

    • Using new and delete with raw pointers is error prone and not safe. In C++ >= C++11, it is safer and better to use RAII technique through the usage of smart pointers.
      • SUMMARY: Use std::unique_ptr or std::shared_ptr instead of new and delete.
    • Dynamic allocation of arrays is often not needed as STL containers already encapsulates the heap allocation and automatically allocate new memory when a new element is added.
      • SUMMARY: Use std::vector, std::array, std::deque instead of dynamically allocated arrays.

    Potential pitfalls:

    • Dangling pointer
    • Null-pointer access
    • Wild pointers
    • Heap corruption
    • Ownership Semantics
    • Non deterministic allocation on embedded systems or real time systems.

    Operators new and delete for a single element

    The following code shows the general usage of the operator new.

    Allocation with exception:

    • Note: The operator new always throws an exception bad_alloc if the allocation fails.
    try{
     Type* object_pointer1 = new Type;
     //  Intialized with constructor arguments
     Type* object_pointer2 = new Type(arg0, arg1, arg2 ...);
     } catch(const std::bad_alloc& ex){
        std::cerr << "Error: failed to allocate memory." << "\n";
        std::cerr << "Error = " << ex.what() << "\n";
        exit(1);
     }
    

    Allocation without exception:

    • When the allocation fails, the operator returns a null pointer.
    Type* object_pointer1 = new (nothrow) Type;
    //  Intialized with constructor arguments
    Type* object_pointer2 = new (nothrow) Type(arg0, arg1, arg2 ...);
    if(!object_pointer1){
       std::cerr << "Error: failed to allocate memory for obj1." << "\n";
       exit(1);
    }
    if(object_pointer1 == nullptr){
       std::cerr << "Error: failed to allocate memory for obj1." << "\n";
       exit(1);
    }
    

    Deallocation:

    • After the heap-allocated object is no longer needed, it is necessary to release the allocated memory by using the delete operator.
    delete object_pointer1;
    // A good practice is to set the pointer to null
    // after the object is released.
    object_pointer1 = nullptr;
    
    // Deleting a null pointer is always safe and does nothing.  
    delete object_pointer1;
    

    Operators new and delete for array allocation

    An array of elements of type Type can be allocated on the heap with:

    Allocation with exception:

    const size_t SIZE = 10;
    try {
       Type* array_pointer = new Type [SIZE];
    } catch(const std::bad_alloc& ex) {
       std::cerr << "Error: failed to allocate memory." << "\n";
       std::cerr << "Error = " << ex.what() << "\n";
    }
    

    Allocation without exception:

    const size_t SIZE = 10;
    Type* array_pointer = new (nothrow) Type [SIZE];
    if(!array_pointer){
      std::cerr << "Error: failed to allocate memory." << "\n";
      exit(1);
    }
    // Or more explicitly
    if(array_pointer == nullptr){
      std::cerr << "Error: failed to allocate memory." << "\n";
      exit(1);
    }
    

    Deallocation:

    delete [] array_pointer;
    
  2. Heap allocation for primitive types

    Allocation of single value on the heap:

    >> double* ptr = new (nothrow) double;
    
    >> if(ptr != nullptr) std::cout << "OK, proceed" << "\n";
    OK, proceed
    >> 
    
    >> *ptr = 100.0;
    
    >> *ptr
    (double) 100.00000
    >> 3.0 * *ptr
    (double) 300.00000
    >> 
    
    // Always delete the pointer when the 
    // allocated object is no longer needed.
    >> delete ptr;
    
    // Always set the pointer to null 
    >> ptr = nullptr;
    
    // Deleting a null pointer is safe and does nothing.
    >> delete ptr;
    

    Array allocation on the heap:

    • Allocate array.
    size_t n = 6;
    
    // Array of size 6
    int* arr = new (nothrow) int [n];
    
    >> arr
    (int *) 0x216cbf0
    
    // Not initialized, can have any value. 
    >> arr[0]
    (int) 36003472
    >> arr[1]
    (int) 0
    >> arr[2]
    (int) 1864399218
    >> 
    
    • Set array elements:
    >> arr[0] = 10;
    >> arr[0] = 0, arr[1] = 15, arr[2] = 20, arr[3] = 90;
    >> arr[4] = 4, arr[5] = 3;
    
    >> arr[0]
    (int) 0
    >> arr[3]
    (int) 90
    >> arr[5]
    (int) 3
    >> 
    
    
    • Access array elements with pointer offset.
    // Access with pointer + offset 
    >> *(arr + 0)
    (int) 0
    >> *(arr + 1)
    (int) 15
    >> *(arr + 2)
    (int) 20
    >> *(arr + 3)
    (int) 90
    >> *(arr + 4)
    (int) 4
    >> *(arr + 5)
    (int) 3
    >> 
    
    >> *(arr + 3) = 100;
    >> arr[3]
    (int) 100
    >> 
    
    • Delete heap-allocated array.
    >> delete [] arr;
    
    // Avoid dangling pointer
    >> arr = nullptr;
    
  3. Heap allocation for objects

    Consider the following testing class:

    #include <iostream>
    #include <string>
    
    class SomeClass{
       // Internal state 
       std::string _id; 
    public:
       // Default constructor is created by the compiler
       // if not defined. 
       SomeClass()
         : _id("unnamed") {
          std::cout << "[LOG] CTOR0 - Object created = <" << _id << "> \n"; 
       }
       SomeClass(const std::string& id)
          : _id(id) 
       {
          std::cout << "[LOG] Object created = <" << _id << "> \n"; 
       }
       std::string getID(){ 
          return _id;  
       }
       void setID(const std::string& id){ 
          _id = id;  
       }
       // Copy-constructor is created by default by the compiler,
       SomeClass(const SomeClass& rhs){
         std::cout << "[LOG] Copy-constructo Object copyed = <" << rhs._id << ">\n";
         this->_id = rhs._id;
       }
       // Copy-assignment operator -
       SomeClass& operator=(const SomeClass& rhs){
         std::cout << "[LOG] Copy-assign operator Object copyed = <" << rhs._id << ">\n";
         this->_id = rhs._id;
         return *this;
       }
    
       // Destructor - is always generated by compiler
       // if not specified. 
       ~SomeClass()
       {
          std::cout << "[LOG] I was deleted <" << _id << "> " << "\n";
       }
    };
    

    Creating a single object on the heap:

    Create object:

     // SomeClass* heapPtr = new (nothrow) SomeClass
    >> SomeClass* heapPtr = new (nothrow) SomeClass("heap-obj1")
    [LOG] Object created = <heap-obj1> 
    (SomeClass *) 0x2c10fe0
    >> 
    
    >> if(heapPtr != nullptr) std::cout << " [LOG] Everything OK." << "\n";
     [LOG] Everything OK.
    >> 
    
    >> heapPtr->getID()
    (std::string) "heap-obj1"
    >> 
    >> heapPtr->setID("Object100")
    >> heapPtr->getID()
    (std::string) "Object100"
    >> 
    >> (*heapPtr).getID()
    (std::string) "Object100"
    >> 
    

    Delete object:

    • The delete operator always invokes destructor.
    >> delete heapPtr;
    [LOG] I was deleted <Object100> 
    
    >> heapPtr = nullptr;
    

    Creating an array of objects on the heap

    • Create array:
    >> SomeClass* p = new (nothrow) SomeClass[3];
    [LOG] CTOR0 - Object created = <unnamed> 
    [LOG] CTOR0 - Object created = <unnamed> 
    [LOG] CTOR0 - Object created = <unnamed> 
    >> 
    
    >> p[0].getID()
    (std::string) "unnamed"
    >> p[1].getID()
    (std::string) "unnamed"
    >> p[2].getID()
    (std::string) "unnamed"
    >> 
    
    • Access elements
    // Comman operator.
    >> p[0].setID("obj0"), p[1].setID("obj1"), p[2].setID("obj2");
    
    >> p[0].getID()
    (std::string) "obj0"
    
    >> p[1].getID()
    (std::string) "obj1"
    
    >> p[2].getID()
    (std::string) "obj2"
    >>
    
    
    • Access elements by pointer offset
      • Note: It is possible because the array is a pointer to the first allocated element. Thus, an array is not aware of its size that must be remembered and passed as function argument alongside the array.
    // Get ID of first element 
    >> (p + 0)->getID()
    (std::string) "obj0"
    >> 
    >> p->getID()
    (std::string) "obj0"
    
    >> (p + 1)->getID()
    (std::string) "obj1"
    
    >> (p + 2)->getID()
    (std::string) "obj2"
    
    >> (p + 0)->setID("elem-zero");
    >> (p + 1)->setID("elem-one");
    >> (p + 2)->setID("elem-two");
    
    >> (p + 0)->getID()
    (std::string) "elem-zero"
    >> (p + 1)->getID()
    (std::string) "elem-one"
    >> (p + 2)->getID()
    (std::string) "elem-two"
    
    • Array assignment
    >> p[0] = SomeClass("objA");
    [LOG] Object created = <objA> 
    [LOG] Copy-assign operator Object copyed = <objA>
    [LOG] I was deleted <objA> 
    
    >> p[0].getID()
    (std::string) "objA"
    >> 
    
    >> p[1] = {"ObjB"}; // Uniform initialization
    [LOG] Object created = <ObjB> 
    [LOG] Copy-assign operator Object copyed = <ObjB>
    [LOG] I was deleted <ObjB> 
    >> 
    
    >> p[1].getID()
    (std::string) "ObjB"
    >> 
    
    
    • Pass array to function:
    void showArray(size_t n, SomeClass xs []){
      for(int i = 0; i < n; i++)
        std::cout << "obj[" << i << "] = " << xs[i].getID() << ", ";
      std::cout << "\n";
      std::cout.flush();
    }
    
    >> showArray(3, p)
    obj[0] = objA, obj[1] = ObjB, obj[2] = elem-two, 
    >> 
    
    • Release memory by deleting the heap-allocated array.
    >> delete [] p;
    [LOG] I was deleted <elem-two> 
    [LOG] I was deleted <ObjB> 
    [LOG] I was deleted <objA> 
    
    >> p = nullptr;
    >> 
    

1.16.4 Limits of stack and heap allocation

This small program aims to test the limits of stack and heap allocation by allowing the user to set the size of a float point array and whether its allocation will happen on stack or heap using pre-processor directives.

File: file:src/stack-allocation-test.cpp

// File:   stack-allocation-test 
// Brief:  Test limits of stack or heap allocation. 
// Author: Caio Rodrigues
//------------------------------------------------------
#include <iostream>
#include <string> 

#ifndef ARRAY_SIZE
   #define ARRAY_SIZE 8000
#endif 

int main(){
    size_t sizeKB = static_cast<size_t>(ARRAY_SIZE) * sizeof(double) / 1024;
    std::cout << " [LOG] Number of elements = " << ARRAY_SIZE << "\n";
    std::cout << " [LOG] Array size in Kbytes = " << sizeKB << "\n";
    #if !defined HEAP || HEAP == 0
       // ===> Stack allocation 
       std::cout << " [LOG] Stack allocation." << "\n";
       double array [ARRAY_SIZE];
    #else
       // ===> Heap allocation 
       std::cout << " [LOG] Heap (dynamic memory) allocation." << "\n";
       // Note: It throws std::bad_alloc exception. 
       double* array = new double [ARRAY_SIZE];       
       delete [] array;
    #endif
       std::cout << " [LOG] Program ended gracefully OK." << "\n";
    return 0;
}

Testing stack-allocation limit:

  • Note: Tested on Linux-64 bits - Kernel 4.16.3
  • Result: Core dump due to stack overflow happens when the array size is 10,000,000, 10 million doubles or 7812 kb (kbytes) or 7.6 Mb (Megabytes). This behavior depends on the operating system and may be not be the same on other operating systems or new Linux's kernel versions.
$ clang++ stack-allocation-test.cpp -o out.bin -DHEAP=false -DARRAY_SIZE=100 && ./out.bin
 [LOG] Number of elements = 100
 [LOG] Array size in Kbytes = 0
 [LOG] Stack allocation.
 [LOG] Program ended gracefully OK.

$ clang++ stack-allocation-test.cpp -o out.bin -DHEAP=false -DARRAY_SIZE=1000000 && ./out.bin
 [LOG] Number of elements = 1000000
 [LOG] Array size in Kbytes = 7812
 [LOG] Stack allocation.
 [LOG] Program ended gracefully OK.

$ clang++ stack-allocation-test.cpp -o out.bin -DHEAP=false -DARRAY_SIZE=10000000 && ./out.bin
Segmentation fault (core dumped)

$ ./out.bin
Segmentation fault (core dumped)

Testing heap (dynamic memory) allocation limit:

  • Note: Tested on Linux-64 bits - Kernel 4.16.3
  • Result: bad_alloc exception happens when the array size is 10 billions or 78125000 kb (= 76293 Mb or 74 GB. The limit of the heap allocation is the size of the virtual memory or RAM memory.
# 10 millions
$ clang++ stack-allocation-test.cpp -o out.bin -DHEAP=true -DARRAY_SIZE=10000000 && ./out.bin
 [LOG] Number of elements = 10000000
 [LOG] Array size in Kbytes = 78125
 [LOG] Heap (dynamic memory) allocation.
 [LOG] Program ended gracefully OK.

# 100 millions 
$ clang++ stack-allocation-test.cpp -o out.bin -DHEAP=true -DARRAY_SIZE=100000000 && ./out.bin
 [LOG] Number of elements = 100000000
 [LOG] Array size in Kbytes = 781250
 [LOG] Heap (dynamic memory) allocation.
 [LOG] Program ended gracefully OK.

# 10 billions
$ clang++ stack-allocation-test.cpp -o out.bin -DHEAP=true -DARRAY_SIZE=10000000000 && ./out.bin
 [LOG] Number of elements = 10000000000
 [LOG] Array size in Kbytes = 78125000
 [LOG] Heap (dynamic memory) allocation.
terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc
Aborted (core dumped)

1.16.5 New and delete overloading functions

  1. Overloadings for new and delete operators

    Global new and delete operators free functions

    • size_t => Size of object being allocated in bytes. This parameter is implicitly passed by the compiler.
    • Returns => A void* pointer to the beginning of the allocated memory block.
    • Note: The operator 'new' allocates objects in the heap memory segment.

    Overloading for new operator

    // Signature 
    void* operator new (std::size_t size);
    
    // Example: allocate single object
    //--------------------------------------------------//
    BaseClass* ptr = nullptr;
    try {
        ptr = new DerivedClassA(param0, param1, param2, ...);
        ptr->CallVirtualMethod1(); 
        delete ptr;    // Always delete when the object is no longer needed 
        ptr = nullptr; // Always set to nullptr 
    catch(std::bad_alloc const& ex) {
        printf(" [INFO] Allocation failed.")
    }
    
    • Operator new for single object, without exception
      • => Returns null pointer nullptr on failure. The second parameter is type tag, used only for making the type signature different.
     // Signature 
    void* operator new (std::size_t size, const std::nothrow_t& tag);
    
    // Example: allocate single object
    //--------------------------------------------------//
    BaseClass* ptr = new (std::nowthrow) DerivedClassA(param0, param1, param2, ...);
    ptr->CallVirtualMethod1(); 
    if(!ptr){ 
        std::printf(" [FAILURE] Abort operation. \n");
        return;
    }
    delete ptr;    
    
    • Operator new for array allocation, throws std::bad_alloc exception.
    // Signature 
    void* operator new[] (std::size_t size);
    
    // Example: => Allocates 10 integers 
    //----------------------------------------------------
    int* pArray = nullptr;
    try{ 
      pArray = new int[10]
    } catch (std::bad_alloc const& ex) {
      // Abort operation 
      return; 
    }
    pArray[0] = 100; pArray[2] = 25; 
    delete [] pArray; // Always call delete 
    pArray = nullptr; // Always set the pointer to nullptr after calling delete
    
  2. Example: Raw memory allocation with new operator

    The 'new' operator can be used for raw memory allocation without calling constructor, in the same way as the C-function malloc.

    Experiment 1

    Allocating an array in heap for 4 integers. (CERN's Root REPL)

    >> void* pi = ::operator new (4 * sizeof(int))
    (void *) 0x3184af0
    
    >> assert(pi != nullptr);
    
    >> int* pint = static_cast<int*>(pi)
    (int *) 0x3184af0
    
    >> pint[0] = 20, pint[1] = 100, pint[2] = 3, pint[3] = 25;
    
    >> pint[0]
    (int) 20
    
    >> pint[1]
    (int) 100
    
    >> pint[2]
    (int) 3
    
    >> pint[3]
    (int) 25
    >> 
    
    >> std::for_each(pint, pint + 4, [](int x){ std::printf(" x = %d \n", x); });
     x = 20 
     x = 100 
     x = 3 
     x = 25 
    
    // Call operator delete 
    >> delete pint
    
    // Set to null when no longer used. 
    >> pi = nullptr; 
    >> pint = nullptr;
    

    Experiment 2

    Allocating raw memory for an instance of a class with new operator:

    struct Circle{
       int x; 
       int y;  
       int radius;
    
       Circle(): x(10), y(20), radius(2) 
       { 
          std::printf(" [INFO] Cirlce constructed OK.\n");
       }
    
       ~Circle()
       { 
          std::printf(" [INFO] Cirlce destroyed OK.\n");
       }
    
       void set_radius(int r) 
       {
          radius = r;
       }
    
       void plot() const 
       { 
          std::printf(" [INFO] Draw circle at Point(%d, %d) with radius = %d\n"
             , this->x, this->y, this->radius);
       }
    };
    

    REPL Session part 1:

    • The following call to operator 'new' only allocates memory for the object without calling its constructor.
    // Constructor is not called!!! 
    // =>> Throws std::bad_alloc on failure. 
    >> void* p = ::operator new(sizeof(Circle))
    (void *) 0x1f30150
    
    assert(p != nullptr); 
    
    // Constructor is not called!!!
    >> Circle* cp1 = (Circle*) p
    (Circle *) 0x1f30150
    
    // =========>>> Manually Initialize Object <<=============//
    //--------------------------------------------------------- 
    
    
    // Solution: Initialize object manually. 
    >> cp1->x = 10;
    >> cp1->y = 20;
    >> cp1->radius = 25;
    
    >> cp1->plot()
     [INFO] Draw circle at Point(10, 20) with radius = 25
    >> 
    
    // =========>>> Destroy object and release memory <<=============//
    //----------------------------------------------------------------- 
    
    // Call destructor when the object is no longer needed
    >> delete cp1
     [INFO] Cirlce destroyed OK.
    
    // Set pointer to null 
    >> cp1 = nullptr
    

    REPL Session Part 2:

    • The constructor could be called if the placement-new operator was used.
    >> void* pp = ::operator new(sizeof(Circle))
    (void *) 0x32acf00
    
    // Abort computation if pp is null. 
    assert( pp != nullptr ); 
    
    // Now constructor is called. 
    
    >> Circle* cpp = new (pp) Circle
     [INFO] Cirlce constructed OK.
    (Circle *) 0x32acf00
    
    >> cpp->x
    (int) 10
    
    >> cpp->y
    (int) 20
    
    >> cpp->plot()
     [INFO] Draw circle at Point(10, 20) with radius = 2
    
    >> delete cpp
     [INFO] Cirlce destroyed OK.
    
  3. Example: User-defined new and delete operators

    It is possible to override the global new and delete operators free functions.

    • Note: replacing the default new and delete operators is not recommended.
    • Instead of replacing the default new and delete operators, a better alternative is defining class-specific operators as static member functions.

    File: new-delete-experiment1.cpp

    Class SomeClass:

    class SomeClass{
    public:
        int x;
        int y;
        char buffer[200];
    
        SomeClass(): x(125), y(2561), buffer("Hello world")
        {
            std::puts(" [TRACE] SomeClass object created OK");
        }
    
        void show() const
        {
            std::printf(" =>> SomeClass { x = %d, y = %d, buffer = %s } \n", x, y, buffer);
        }
    
        ~SomeClass()
        {
            std::puts(" [TRACE] SomeClass object destroyed OK");
        }
    };
    

    Class SomeClassB:

    class SomeClassB{
        char m_name[200] = "Unnamed";
        int  m_id = -1;
    
        SomeClassB()
        {
            std::printf(" [TRACE] <CTOR1> Object of SomeClassB created => addr = %p "
                        " id = %d - name = '%s'"
                        , static_cast<void*>(this), m_id, m_name);
        }
    
        ~SomeClassB()
        {
            std::printf(" [TRACE] <DTOR> Object of SomeClassC deleted => addr = %p \n"
                        , static_cast<void*>(this));
        }
    
    };
    

    Class SomeClassC:

    • This class has class-specific new and delete operators defined as static member functions. In this case, The keyword static case can be omitted, but it was used for making the code more explicit.
    class SomeClassC {
    public:
        char m_name[200] = "Unnamed";
        int  m_id = -1;
    
        SomeClassC()
        {
            std::printf(" [TRACE] <CTOR1> Object of SomeClassC created => addr = %p "
                        " id = %d - name = '%s'"
                        , static_cast<void*>(this), m_id, m_name);
        }
    
        SomeClassC(const char* name, int id)
        {
            std::strcpy(m_name, name);
            m_id = id;
            std::printf(" [TRACE] <CTOR2> Object of SomeClassC created => addr = %p "
                        " id = %d - name = '%s' \n"
                        , static_cast<void*>(this), m_id, m_name);
        }
    
        ~SomeClassC()
        {
            std::printf(" [TRACE] <DTOR> Object of SomeClassC deleted => addr = %p \n"
                        , static_cast<void*>(this));
        }
    
        // Member fuction overloading of new operator
        // -------------------------------------------------------
        // Note: the keyword static is redundant, this operator is a static member function
        // even without the keyword 'static'. It was added to make the declaration more explicit.
        static void* operator new(size_t sz)
        {
            void* ptr = std::malloc(sz);
            if(!ptr) std::puts(" [ERROR] Not enough memory");
            std::printf(" [TRACE] SomeClassB => Custom new operator called. Allocated %lu Bytes => "
                        " ptr = %p \n"
                        , sz, ptr);
            return ptr;
        }
    
        // Member fuction overloading of delete operator
        // Static keyword redundant
        static void operator delete(void* addr)
        {
            std::printf(" [TRACE] Custom delete operator called for SomeClassB => p = %p \n"
                        , addr);
            free(addr);
        }
    };
    

    Global operator new free-function:

    • Note: This overloading throws exceptin std::bad_alloc when there is no enough memory.
    // Global free-function overloading of new operator Note: Cannot be
    // declared inside a namespace Note: DO NOT DO IT!
    void* operator new(size_t size)
    {
        std::printf(" [TRACE] NEW - Called operator new, Allocated %lu Bytes\n", size);
        void* ptr = std::malloc(size);
        if(!ptr) {
            std::puts(" [ERROR] Not enough memory");
            throw std::bad_alloc();
        }
        return ptr;
    }
    

    Global operator new free-function (std::nothrow):

    • Note: This overloading does not throw exception. It just returns a null pointer when there is no enough memory.
    // Global free-function overloading of new operator (std::notrhow)
    // Note: Cannot be declared inside a namespace
    // Note: Does not throw exception, just returns a null pointer when there is no
    //       memory available
    void* operator new(size_t size, std::nothrow_t const& tag)
    {
        std::printf(" [TRACE] NEW (std::nothrow) - Called operator new, Allocated %lu Bytes\n", size);
        void* ptr = std::malloc(size);
        if(!ptr) {
            std::puts(" [ERROR] Not enough memory");
            return nullptr;
        }
        return ptr;
    }
    

    Global operator delete free function:

    // Global free-function overloading of delete operator
    // Note: Cannot be delcared inside a namespace
    void operator delete(void* ptr) noexcept
    {
        std::puts(" [TRACE] DELETE - Called operator delete");
        free(ptr);
    }
    

    Main Function / Experiment 1

    • Allocate primitive type with custom new operator.
    std::puts("\n ==>> Experiment 1 == Global overloading -test with primitive type \n");
    {
        int* ptr = new int(25);
        *ptr = 10 + *ptr;
        std::printf(" Value of ptr[%p] = %d \n", static_cast<void*>(ptr), *ptr);
        delete  ptr;
    }
    

    Output:

    ==>> Experiment 1 == Global overloading -test with primitive type 
    
    [TRACE] NEW - Called operator new, Allocated 4 Bytes
    Value of ptr[0x2478e80] = 35 
    [TRACE] DELETE - Called operator delete
    

    Main Function / Experiment 2

    • Allocate and instantiate an object of class SomeClass in the process heap using the user-defined global new (std::bad_alloc) operator. Note: The default constructor is called.
    std::puts("\n ==>> Experiment 2 == Global Overloading - test with class ====\n");
    {
        SomeClass* pcls = new SomeClass;
        pcls->x = 25;
        pcls->y = 1005;
        pcls->show();
        delete pcls;
    }
    

    Output:

    ==>> Experiment 2 == Global Overloading - test with class ====
    
    [TRACE] NEW - Called operator new, Allocated 208 Bytes
    [TRACE] SomeClass object created OK
    =>> SomeClass { x = 25, y = 1005, buffer = Hello world } 
    [TRACE] SomeClass object destroyed OK
    [TRACE] DELETE - Called operator delete
    

    Main Function / Experiment 3

    • Allocate and instantiate an object of class SomeClass in the process heap using the user-defined global new (std::nothrow) operator. Note: The default constructor is called.
    std::puts("\n ==>> Experiment 3 == (std::notrhow) Global Overloading - test with class ====\n");
    {
        SomeClass* pcls = new (std::nothrow) SomeClass;
        pcls->x = 25;
        pcls->y = 1005;
        pcls->show();
        delete pcls;
    }
    

    Output:

    ==>> Experiment 3 == (std::notrhow) Global Overloading - test with class ====
    
     [TRACE] NEW (std::nothrow) - Called operator new, Allocated 208 Bytes
     [TRACE] SomeClass object created OK
     =>> SomeClass { x = 25, y = 1005, buffer = Hello world } 
     [TRACE] SomeClass object destroyed OK
     [TRACE] DELETE - Called operator delete
    

    Main Function / Experiment 4

    • The global operator new is used only for raw allocation, in the same way as malloc. The object is not instantiated and no constructor is called.
    std::puts("\n ==>> Experiment 4 - Explicit raw allocation  == Global Overloading - test with class ====\n");
    {
        // Raw memory allocation => Without calling the constructor
        void* raw_pcls = ::operator new(sizeof(SomeClass));
        // Note: The constructor is not called
        SomeClass* pcls = static_cast<SomeClass*>(raw_pcls);
        pcls->x = 251;
        pcls->y = -1005;
        std::strcpy(pcls->buffer, "raw-memory-allocated");
        pcls->show();
        delete pcls;
    }
    

    Output:

    ==>> Experiment 4 - Explicit raw allocation  == Global Overloading - test with class ====
    
    [TRACE] NEW - Called operator new, Allocated 208 Bytes
    =>> SomeClass { x = 251, y = -1005, buffer = raw-memory-allocated } 
    [TRACE] SomeClass object destroyed OK
    [TRACE] DELETE - Called operator delete
    

    Main Function / Experiment 5

    • Calls new and delete operators specific for SomeClassC defined as static meber functions.
    std::puts("\n ==>> Experiment 5 === Member Function Overloading ==========\n");
    {
        SomeClassC* pclassB = new SomeClassC("Hello", 2005);
        std::printf(" =>>  Print object => pclassB->m_name = %s; pclassB->m_id = %d \n"
                    , pclassB->m_name, pclassB->m_id);
    
        delete pclassB;
    }
    

    Output:

    ==>> Experiment 5 === Member Function Overloading ==========
    
    [TRACE] SomeClassB => Custom new operator called. Allocated 204 Bytes =>  ptr = 0x2478ea0 
    [TRACE] <CTOR2> Object of SomeClassC created => addr = 0x2478ea0  id = 2005 - name = 'Hello' 
    =>>  Print object => pclassB->m_name = Hello; pclassB->m_id = 2005 
    [TRACE] <DTOR> Object of SomeClassC deleted => addr = 0x2478ea0 
    [TRACE] Custom delete operator called for SomeClassB => p = 0x2478ea0 
    

1.16.6 Placement new and delete operators

  1. Overview

    The placement new operator has an additional parameter void* which allows instantiating a type or class in a specific memory location.

    Notes:

    • This operator does not allocate any storage. It instantiantes object at a pre-allocated storage or at specific memory address.
    • The global new and delete placement operators cannot be replaced by user-defined ones.

    Some use-cases for this operator are:

    • Instantiate object in shared memory between two processes.
    • Instantiate object in memory mapped file.
    • Instantiate object in some pre-allocated buffer, it allows instantiating polymorphic objects at runtime in a static-allocated buffer (global variable) or in a stack-allocated buffer. This technique is useful in embedded systems for enabling polymorphism without heap allocation.
      • Note: A buffer in this cases is a byte array: char buffer [BUFFER_SIZE]
    • Instantiate object in some specific hardware address - Memory Mapped IO, MMIO (embedded systems)
    • Custom allocators

    Placemente new and delete operators for single object

    • The parameter size is the size of the data type to be allocated.
    • The pointer place holds the address where the object will be instantiated.
    • The return value is the pointer to the allocated object.
    • Note: This gloabl new placement operator cannot be replaced by an user-defined one. However, it is possible to define class-specific one as static member function.
    // Placement new operator:
    void* operator new(std::size_t size, void* location); 
    
    // Placement delete operator: 
    void delete(void* p_object, void* location);
    

    Usage example:

    // Allocate object in pre-allocated buffer 
    constexpr size_t BUFFER_SIZE = 2048; // 2048 bytes or 2kb
    char buffer[BUFFER_SIZE];
    
    // Construct object at buffer 
    BaseClass* obj = new (buffer) DerivedClass(arg0, arg1, arg2, ... argN-1)
    
    obj->callVirtualMethod(); 
    
    // Invoke destructor when object is no longer needed
    obj->~BaseClass();  
    
    // Call placement delete operator 
    ::operator delete(obj, buffer);
    

    Placemente new and delete operators for arrays

    // Placement new operator for arrays
    void* operator new[](std::size_t size, void* location); 
    
    // Placement new operator for arrays 
    void* operator delete[](void* p_object, void* location); 
    
  2. Example - placement-new experiment in REPL

    Note: This example was run in the CERN's Root REPL.

    Class Dummmy:

    class Dummy{   
    public:
        int x = 200;
        int y = 25;
        char name[200] = "Unnamed";
    
        Dummy()
        {
            std::printf(" [INFO] Default ctor called. => this = %p \n"
                      , static_cast<void*>(this));
        }
    
        Dummy(int x, int y, const char* name)
        {
            std::strcpy(this->name, name);
            this->x = x;
            this->y = y;
        }
    
        ~Dummy()
        {
            std::printf(" [INFO] Dtor called. => this = %p \n"
                        , static_cast<void*>(this));
        }
    
        void show() const
        {
            std::printf(" [INFO] Dummy { name = '%s' ; x = %d; y = %d } \n"
                        , this->name, this->x, this->y);
        }
    };
    

    REPL Session:

    Get size of type 'Dummy' in bytes

    >> sizeof(Dummy)
    (unsigned long) 208
    

    Pre-allocated static (global) or stack buffer large enough for storing an instance of class dummy.

    // Create a buffer with 416 bytes 
    std::uint8_t buffer[416];
    

    Buffer before instantiation:

    >> buffer
    (std::uint8_t [416]) { '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', .... '0x00', '0x00' }
    
    >> (int) buffer[0]
    (int) 0
    
    >> (int) buffer[1]
    (int) 0
    
    >> (int) buffer[2]
    (int) 0
    >> 
    

    Use the placment-new operator for creating an object of class Dummy in the buffer.

    >> Dummy* p = new (buffer) Dummy
     [INFO] Default ctor called. => this = 0x7fda9fe7a010 
    (Dummy *) 0x7fda9fe7a010
    
    >> p->show()
     [INFO] Dummy { name = 'Unnamed' ; x = 200; y = 25 } 
    >> 
    
    >> p->x
    (int) 200
    
    >> p->y
    (int) 25
    
    >> p->name
    (char [200]) "Unnamed\0\0\0\0\0\0\0\0 ... ... \0\0\0"
    

    Buffer after instantiation:

    >> buffer
    (std::uint8_t [416]) { 
      '0xc8', '0x00', '0x00', '0x00', '0x19', '0x00', '0x00', '0x00', 'U', 'n', 'n', 'a', 'm', 'e', 'd',
      '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00' .... ... '0x00'}
    
    >> (int) buffer[0]
    (int) 200
    
    >> (int) buffer[4]
    (int) 25
    

    Manipulating object variable:

    >> int xx1 = 0x4ABC7852
    (int) 1253865554
    
    >> int yy1 = 0x3DFCA3E2
    (int) 1039967202
    
    >> p->x = xx1;
    >> p->y = yy1;
    >> std::strcpy(p->name, "I_am_dummy_object_in_C++1z");
    
    >> p->show()
     [INFO] Dummy { name = 'I_am_dummy_object_in_C++1z' ; x = 1253865554; y = 1039967202 } 
    

    Check buffer:

    >> buffer
    (std::uint8_t [416]) { 'R', 'x', '0xbc', 'J', '0xe2', '0xa3', '0xfc'
    , '=', 'I', '_', 'a', 'm', '_', 'd', 'u', 'm', 'm', 'y', '_', 'o', 'b', 'j', 'e'
    , 'c', 't', '_', 'i', 'n', '_', 'C', '+', '+', '1', 'z', '0x00', '0x00', '0x00', '0x00' .. ... }
    

    Create function display_buffer for showing buffer content in decimal, hexadecimal and char format:

    void display_bytes(std::uint8_t* buffer, size_t min, size_t max){
        if(min > max) throw std::runtime_error("Error: supposed min <= max");
        for(size_t i = min; i < max; i++)
        {
            char ch = std::isprint(buffer[i]) ? buffer[i] : ' ';
            std::printf(" => byte[%lu] = 0x%X %d %c\n", i
                        , static_cast<int>(buffer[i])
                        , static_cast<int>(buffer[i])
                        , ch);
        }
    }
    

    Analyze buffer bytes 0 to 3:

    >> printf(" => xx1 = %X \n", xx1);
     => xx1 = 4ABC7852 
    
    // Value of p->x == 0x4ABC7852 
    // 0x 4A BC 78 52 
    >> display_bytes(buffer, 0, 4)
     => byte[0] = 0x52 82 R
     => byte[1] = 0x78 120 x
     => byte[2] = 0xBC 188  
     => byte[3] = 0x4A 74 J
    
    >>  int x1_value = (buffer[3] << 24) + (buffer[2] << 16) + (buffer[1] << 8) + buffer[0]
    (int) 1253865554
    
    >> p->x
    (int) 1253865554
    
    x1_value == p->x
    (bool) true
    
    >> std::printf(" p->x = %X ; x1_value = %X \n", p->x, x1_value);
     p->x = 4ABC7852 ; x1_value = 4ABC7852 
    

    Analyze buffer bytes 4 to 7:

    >> printf(" => yy1 = %X \n", yy1);
     => yy1 = 3DFCA3E2 
    
    // p->y = 0x3DFCA3E2 
    // Bytes: 0x 3D FC A3 E2 
    // In the buffer, the bytes are stored in "Little Endian Format" 
    // as E2 A3 FC 3D
    >> display_bytes(buffer, 4, 8)
     => byte[4] = 0xE2 226  
     => byte[5] = 0xA3 163  
     => byte[6] = 0xFC 252  
     => byte[7] = 0x3D 61 =
    
    >> int y1_value = (buffer[7] << 24) + (buffer[6] << 16) + (buffer[5] << 8) +  buffer[4]
    (int) 1039967202
    
    >> printf(" p->y = %X ; y1_value = %X \n", p->y, y1_value);
     p->y = 3DFCA3E2 ; y1_value = 3DFCA3E2 
    

    Copy character portion to a std::string object.

    >> std::string str(buffer + 8, buffer + 40);
    
    >> str
    (std::string &) "I_am_dummy_object_in_C++1z\0\0\0\0\0\0"
    >> 
    

    Release memory:

    // Call destructor 
    >> p->~Dummy() 
     [INFO] Dtor called. => this = 0x7ff08c52c010 
    
    // Call placement-delete operator 
    >> ::operator delete(p, buffer)
    

1.16.7 References and further reading

General:

Placement New and Delete Opeators:

C++ Idioms related to Heap-Allocation:

Debugging:

1.17 OOP Object Lifecycle

This code shows how object lifecycle works in C++ by instrumenting constructors and destructors member functions.

C++ can allocated objects in three different areas or memories:

  • stack - Objects allocated by default on the stack when instantiated inside functions, member functions or local scope. Objects instantiated on the stack are automatically deleted or released when they go out of scope and then the destructor is called.
    • Summary and further notes:
      • Objects allocated on the stack are automatically released when they go out of scope.
      • There is a size limit for objects allocated on the stack which depends on the operating system, therefore very large objects allocated on the stack can cause segmentation fault. If it is the case, it is better to allocate the object on the heap or dynamic memory.
  • static memory - Objects, declared outside functions or member functions are instantiated on the static memory and they are only deleted when the program finishes.
  • heap memory - Objects are allocated on the heap memory (aka dynamic memory) when they are instantiated with operator new. Unlike stack or static allocated objects, heap allocated objects are not automatically destroyed, so they must be released manually with the delete operator. However, deleting heap-objects manually is error prone since it is easy to forget to call delete operator. A better approach to deal with heap object is to use C++ smart pointers that wraps raw pointers and automatically deletes heap objects when they go out of scope.

Code Highlights:

  • Object allocated in static memory - lives during the entire program execution and are only destroyed when the program execution ends.
DummyClass dummyGlobal("dummy-global");

int main(){ 
  ... ... 
};
  • Object allocated in stack: (dummy1, dummy2, object d in testObject())
 // ... 
int main(){
    // Allocated on stack 
    DummyClass dummy1("dummy1-stack");

    {
      std::cerr << "\n" << " ---- ENTER LOCAL SCOPE " << "\n\n";
      TRACE("Create local scope");
      // Allocated on stack 
      DummyClass dummy2("dummy2-stack-local-scope");
          ... ... .. 
      std::cerr << "\n" << "EXIT LOCAL SCOPE " << "\n\n";
    } // dummy2 deleted here 

} // dummy1 is deleted here 
  • Objects allocated on the heap (dummyInHeap) must be released manually or automatically with smart pointers (not used in this code).
    • Note: For every object allocated on the heap, it is necessary a delete operator if smart pointers are not used.
auto makeDummyHeap() -> DummyClass* {
  // ... ... 
  // Heap-allocated object 
  DummyClass* ptr = new DummyClass("dummy-heap");
  // ...
  return ptr; 
};

int main(){
  // ... ... ... .
  DummyClass* dummyInHeap =  makeDummyHeap();

  // ....   
  dummyInHeap->speakWithUser();
  // ....

  delete dummyInHeap;
}

  • Deterministic destructor
    • In C++, destructors are deterministic. They are always called automatically when an object allocated on the static memory (global object) or stack goes out of scope or an exception happens. Due to this feature, it is safe to release resources like allocated memory, database handlers, file handlers, socket handlers or perform other cleanup tasks on the destructor. It is the foundation of the RAAI technique/idiom - "Resource Acquisition is Initialization".
try {
     std::cerr << "\n" << " ---- ENTER LOCAL EXCEPTION SCOPE " << "\n\n";
     DummyClass dummyException("dummy2-stack-local-scope");
     dummyException.speakWithUser();
     throw std::runtime_error(" ERROR Throw a failure for testing deterministic destructor");
     std::cerr << "\n" << " ---- EXIT LOCAL EXCEPTION SCOPE " << "\n\n";
} catch (const std::runtime_error& ex){
     std::cerr << "\n" << " ---- ENTER EXCEPTION HANDLER" << "\n\n";        
     std::cerr << "Failure = " << ex.what() << "\n";
     std::cerr << "\n" << " ---- EXIT EXCEPTION HANDLER" << "\n\n";
}

Output:

 ---- ENTER LOCAL EXCEPTION SCOPE 

object-lifecycle.cpp:21: - fun = DummyClass ; name = dummy2-stack-local-scope - I was created.
I am a dummy object called = dummy2-stack-local-scope
object-lifecycle.cpp:54: - fun = ~DummyClass ; name = dummy2-stack-local-scope - I was destroyed

 ---- ENTER EXCEPTION HANDLER

Failure =  ERROR Throw a failure for testing deterministic destructor

 ---- EXIT EXCEPTION HANDLER

Complete Code:

// File:  object-lifecycle.cpp 
// Brief: Demonstrate Object Lifecycle 
#include <iostream>
#include <string>

#define DEBUG_TRACE

#ifdef DEBUG_TRACE
  #pragma message "Logging Enabled"
  #define TRACE(msg) \
    std::cerr << __FILE__ << ":" << __LINE__ << ": - fun = " << __FUNCTION__ << " ; " << msg << "\n"
#else
  #pragma message ("Logging disabled")
  #define TRACE(msg)
#endif 

class DummyClass{
public:
        // Constructor 
        DummyClass(const std::string& name): _object_name(name){
                TRACE(std::string("name = ") + name + " - I was created.");
        }
        // Copy constructor 
        DummyClass(const DummyClass& rhs){
                TRACE("Enter copy constructor");
                TRACE("name = " + _object_name + " - I was copied. ");
                this->_object_name = rhs._object_name + "-COPIED";
        }
        // Move constructor 
        DummyClass( DummyClass&& rhs){
                TRACE("Enter move constructor");
                TRACE("name = " + _object_name + " - I was moved");
                this->_object_name = rhs._object_name + "-MOVED";
        }
        // Copy assignment operator
        DummyClass& operator= (const DummyClass& rhs){
                TRACE("Enter copy assignment operator");
                TRACE("name = " + _object_name + " - I was copied. ");
                this->_object_name = rhs._object_name + "-COPIED";
                return *this;
        }
        //DummyClass& operator= (DummyClass&& rhs) = delete;

    // Move assignment operator 
        DummyClass& operator= (DummyClass&& rhs){
                TRACE("Enter move assignment operator");
                TRACE("name = " + _object_name + " - I was moved. ");
                this->_object_name = rhs._object_name + "-MOVED";
                return *this;
        }

        // Destructor 
        ~DummyClass(){
                TRACE(std::string("name = ") + _object_name  + " - I was destroyed");
        }
        void speakWithUser(){
                std::cout << "I am a dummy object called = " << _object_name << "\n";
        }

private:
        std::string _object_name;   
};

// Object allocated on the static memory 
// is deleted when the programs finishes. 
DummyClass dummyGlobal("dummy-global");

auto testObject() -> DummyClass {
        std::cerr << "\n" << " ==> ENTER FUNCTION  testObject()" << "\n\n";
    TRACE("Enter function");
        auto d  = DummyClass("local-dummy-in-function");
        d.speakWithUser();
        std::cerr << "\n" << " ==> EXIT FUNCTION  testObject()" << "\n\n";
    TRACE("Exit function");
        // Object d is deleted here when it goes out scope
        // and then a copy of it is returned from here.
        // Therefore, the copy constructor is invoked.
        return d;
}

auto makeDummyHeap() -> DummyClass* {
        std::cerr << "\n" << " ==> ENTER FUNCTION  makeDummyHeap()" << "\n\n";  
        // Object allocated in dynamic memory, so it survives this scope
        // and is not deleted when returned from function. 
        DummyClass* ptr = new DummyClass("dummy-heap");
        ptr->speakWithUser();
        std::cerr << "\n" << " ==> EXIT FUNCTION  makeDummyHeap()" << "\n\n";
        return ptr; 
};

int main(){
        std::cerr << "\n" << "ENTER FUNCTION MAIN" << "\n\n";
        TRACE("Main function started.");
        // Object allocated on the stack -> auto storage class, it is
        // destroyed when it goes out of scope 
        DummyClass dummy1("dummy1-stack");
        dummy1.speakWithUser();

        DummyClass* dummyInHeap =  makeDummyHeap();
        dummyInHeap->speakWithUser();

        {
                std::cerr << "\n" << " ---- ENTER LOCAL SCOPE " << "\n\n";
                TRACE("Create local scope");
                DummyClass dummy2("dummy2-stack-local-scope");
                dummy2.speakWithUser();
                dummyGlobal.speakWithUser();
                dummyInHeap->speakWithUser();
                TRACE("End local scope");
                // Object dummy2 deleted here
                std::cerr << "\n" << "EXIT LOCAL SCOPE " << "\n\n";
        }

        try {
                std::cerr << "\n" << " ---- ENTER LOCAL EXCEPTION SCOPE " << "\n\n";
                DummyClass dummyException("dummy2-stack-local-scope");
                dummyException.speakWithUser();
                throw std::runtime_error(" ERROR Throw a failure for testing deterministic destructor");
                std::cerr << "\n" << " ---- EXIT LOCAL EXCEPTION SCOPE " << "\n\n";
        } catch (const std::runtime_error& ex){
                std::cerr << "\n" << " ---- ENTER EXCEPTION HANDLER" << "\n\n";     
                std::cerr << "Failure = " << ex.what() << "\n";
                std::cerr << "\n" << " ---- EXIT EXCEPTION HANDLER" << "\n\n";
        }


        TRACE("Copy object returned from function");
        DummyClass dummy2 = testObject();
        dummy2.speakWithUser();

        dummyInHeap->speakWithUser();

        // Objects allocated on the heap must be released manually or a
        // memory leak will happen. However, it is easy to forget to
        // delete a heap-allocated object, so the this approach is error
        // prone and better solution is to use C++11 smart pointers.
        delete dummyInHeap;

        std::cerr << "\n" << "EXIT FUNCTION MAIN" << "\n\n";
        TRACE("Main function finished.");
        return 0;
        // Object dummy1 and dummyGlobal deleted here 
}

Complete Output:

$ g++ object-lifecycle.cpp -o object-lifecycle.bin -g -std=c++1z -Wall -Wextra && ./object-lifecycle.bin

object-lifecycle.cpp:9:19: note: #pragma message: Logging Enabled
   #pragma message "Logging Enabled"
                   ^~~~~~~~~~~~~~~~~
object-lifecycle.cpp:21: - fun = DummyClass ; name = dummy-global - I was created.

ENTER FUNCTION MAIN

object-lifecycle.cpp:93: - fun = main ; Main function started.
object-lifecycle.cpp:21: - fun = DummyClass ; name = dummy1-stack - I was created.
I am a dummy object called = dummy1-stack

 ==> ENTER FUNCTION  makeDummyHeap()

object-lifecycle.cpp:21: - fun = DummyClass ; name = dummy-heap - I was created.
I am a dummy object called = dummy-heap

 ==> EXIT FUNCTION  makeDummyHeap()

I am a dummy object called = dummy-heap

 ---- ENTER LOCAL SCOPE 

object-lifecycle.cpp:104: - fun = main ; Create local scope
object-lifecycle.cpp:21: - fun = DummyClass ; name = dummy2-stack-local-scope - I was created.
I am a dummy object called = dummy2-stack-local-scope
I am a dummy object called = dummy-global
I am a dummy object called = dummy-heap
object-lifecycle.cpp:109: - fun = main ; End local scope

EXIT LOCAL SCOPE 

object-lifecycle.cpp:54: - fun = ~DummyClass ; name = dummy2-stack-local-scope - I was destroyed

 ---- ENTER LOCAL EXCEPTION SCOPE 

object-lifecycle.cpp:21: - fun = DummyClass ; name = dummy2-stack-local-scope - I was created.
I am a dummy object called = dummy2-stack-local-scope
object-lifecycle.cpp:54: - fun = ~DummyClass ; name = dummy2-stack-local-scope - I was destroyed

 ---- ENTER EXCEPTION HANDLER

Failure =  ERROR Throw a failure for testing deterministic destructor

 ---- EXIT EXCEPTION HANDLER

object-lifecycle.cpp:127: - fun = main ; Copy object returned from function

 ==> ENTER FUNCTION  testObject()

object-lifecycle.cpp:70: - fun = testObject ; Enter function
object-lifecycle.cpp:21: - fun = DummyClass ; name = local-dummy-in-function - I was created.
I am a dummy object called = local-dummy-in-function

 ==> EXIT FUNCTION  testObject()

object-lifecycle.cpp:74: - fun = testObject ; Exit function
I am a dummy object called = local-dummy-in-function
I am a dummy object called = dummy-heap
object-lifecycle.cpp:54: - fun = ~DummyClass ; name = dummy-heap - I was destroyed

EXIT FUNCTION MAIN

object-lifecycle.cpp:140: - fun = main ; Main function finished.
object-lifecycle.cpp:54: - fun = ~DummyClass ; name = local-dummy-in-function - I was destroyed
object-lifecycle.cpp:54: - fun = ~DummyClass ; name = dummy1-stack - I was destroyed
object-lifecycle.cpp:54: - fun = ~DummyClass ; name = dummy-global - I was destroyed

1.18 OOP Polymorphism and Inheritance

1.18.1 Overview

Dynamic or subtyping polymorphis is basically, the ability of the client code to deal with any any instance of derived classes in the same way it deals with an instance of the base class.

In C++, it is only possible to use OOP subtyping polymorphism with pointers or references. As a result, it is not possible to store instances of base class in STL containers by value, or to create polymorphic functions which return instance of derived classes by value or take instance derived classes as argument passed by value.

To summarize:

  • Object Oriented Polymorphism, is aso called:
    • Dynamic polymorphism
    • Runtime polymorphism
  • In C++ it is only possible to use polymorphism with references or pointers.
  • A polymorphic function which returns any instance of the derived class casted as an instance of the base class can only return them by pointer to objects allocated on the heap (dynamic memory) with new operator. Functions like this are called factory functions and in this case, it is better to wrap the pointer in smart pointer, unique_ptr or shared_ptr.

1.18.2 OOP - Dynamic Polymorphism in deep

Classes in the script file:src/polymorphism1.C

class Base{
public:
   Base(){}
   // Copy constructor
   Base(const Base& rhs){
       std::cerr << " [TRACE] " << __FILE__ << ":" << __LINE__ << " "
                 << " Base copy constructor invoked." << "\n";
   }
   // Copy assignment operator
   Base& operator= (const Base& rhs){
        std::cerr << " [TRACE] " << __FILE__ << ":" << __LINE__ << " " 
                  << " Base copy assignment operator invoked." << "\n";
        return *this;
   }
   // The base class always need a virtual
   // destructor 
   virtual ~Base() = default;   

   virtual std::string getType() const {
      return "Base";
   }    
   std::string getType2() const {
      return "Base";
   }
   void showType(){
      // OR: std::cout << "Class type = " << getType() << "\n";
      std::cout << "Class type = " << this->getType() << "\n";
   }
};

class DerivedA: public Base{
public:
    DerivedA(){}
    std::string getType() const {
       return "DerivedA";
    }
    std::string getType2() const {
       return "DerivedA";
    }       
};

class DerivedB: public Base{
public:
   DerivedB(){}
   std::string getType() const {
      return "DerivedB";
   }
   std::string getType2() const {
      return "DerivedB";
   }        
};

Load classes from script and create instances.

>> .L polymorphism1.C
Base base;
DerivedA da;
DerivedB db;

Testing methods:

// Test method getType
>> base.getType()
(std::string) "Base"

>> da.getType()
(std::string) "DerivedA"

>> db.getType()
(std::string) "DerivedB"
>> 

// Test method get
>> base.showType()
Class type = Base
>> da.showType()
Class type = DerivedA
>> db.showType()
Class type = DerivedB
>> 

Testing object slicing on assingment by value

  • Object: slicing => Happens when the an object of derived class is assigned to a base class object, the virtual methods overridden in the derived class are sliced off from the derived class. As a result, any method call performed by the assigned object will call the method base version, instead of the method overridden by the derived class.
    • Summary: An object loses its polymorphic abilities when assigned by value.
  • Note: assignment in C++, is different from assignment in Java and other OO languages. In C++, the assignment copies the object from right side to the assigned object discarding all previous state from the assigned object. In Java and other OO languages, the assignment copies the reference (pointer) from the right-hand side object to the left-hand side object, thus the assigned object becomes an alias or mirror to right-hand side object.
>> base = da
 [TRACE] polymorphism1.C:19  Base copy assignment operator invoked.
(Base &) @0x7f9882578010
>> 
// It should print "DerivedA", however due to object slicing, it prints "Base"
>> base.getType()
(std::string) "Base"

>> base.showType()
Class type = Base
>> 
>> 

>> da.getType()
(std::string) "DerivedA"
>> da.showType()
Class type = DerivedA

Assignment by pointer

The solution for avoiding object slicing on assignment is to assign by pointer instead of assigning by value.

>> Base* p = nullptr;

// Point to object base ------------------------------------
>> p = &base;
>> p->getType()
(std::string) "Base"
>> p->getType2()
(std::string) "Base"
>> p->showType()
Class type = Base
>> 

// Point to object da (class DerivedA)
//------------------------------------
>> p = &da;
>> p->getType()
(std::string) "DerivedA"
// Note: overriding doesn't work because getType2() is annotated as virtual· 
>> p->getType2()
(std::string) "Base"
>> p->showType()
Class type = DerivedA
>> 

// Point to object da (class DerivedB)
//------------------------------------
>> p = &db;
>> p->getType()
(std::string) "DerivedB"
>> p->getType2()
(std::string) "Base"
>> p->showType()
Class type = DerivedB
>> 

Assigning by reference

Another solution for keeping the polymorphic behavior and avoid object slicing is using references.

  • Note: Unlike pointers, references cannot be reassigned once they are created.
>> Base& refb = base;

>> refb.getType()
(std::string) "Base"

>> refb.getType2()
(std::string) "Base"

>> refb.showType()
Class type = Base
>> 

// Failure: object slicing!
>> refb = da;
 [TRACE] polymorphism1.C:19  Base copy assignment operator invoked.
>> 
>> refb.getType()
(std::string) "Base"
>> refb.showType()
Class type = Base
>> 

// Now, it works.
>> Base& refda = da;
>> refda.getType()
(std::string) "DerivedA"
>> refda.getType()
(std::string) "DerivedA"
>> refda.showType()
Class type = DerivedA
>> refda.getType2()
(std::string) "Base"

Passing by value

Testing object slicing on passing parameters by value:

  • Object slicing also happens when objects are passed by value.
void printClassNameValue(Base obj){
     std::cout << "Object type is: " << obj.getType() << "\n";
}

>> printClassNameValue(base)
 [TRACE] polymorphism1.C:13  Base copy constructor invoked.
Object type is: Base
>> 
// Should print Object type is: DerivedA 
>> printClassNameValue(da)
 [TRACE] polymorphism1.C:13  Base copy constructor invoked.
Object type is: Base
>> 
// Should print Object type is: DerivedB 
>> printClassNameValue(db)
 [TRACE] polymorphism1.C:13  Base copy constructor invoked.
Object type is: Base
>> 

Passing by Pointer

The solution for avoiding object slicing is passing objects by pointer or reference:

// Pass by pointer => Object slicling doesn't happen.
void printClassNamePtr(const Base* obj){
     std::cout << "Object type is: " << obj->getType() << "\n";
}

>> printClassNamePtr(&base)
Object type is: Base

>> printClassNamePtr(&da)
Object type is: DerivedA

>> printClassNamePtr(&db)
Object type is: DerivedB
>> 
>> Base* ptr = nullptr;
>> ptr = &da;
>> printClassNamePtr(ptr)
Object type is: DerivedA
>> 
>> ptr = &db;
>> printClassNamePtr(ptr)
Object type is: DerivedB
>> 

Passing by Reference

  • Passing by reference
    • Note: In most object oriented languages, objects are passed by reference by default, unlike C++ where objects are passed by value.
void printClassNameRef(const Base& obj){
     std::cout << "Object type is: " << obj.getType() << "\n";
}

>> printClassNameRef(base)
Object type is: Base

>> printClassNameRef(da)
Object type is: DerivedA

>> printClassNameRef(db)
Object type is: DerivedB
>> 

Factory function:

  • A factory function returns any instance of any derived class of a given base class.

Object slicing also happens when polymorphic types are returned by value:

// This factory functon will fail due to object slicing 
Base factoryFunctionValue(const std::string& type){
     if(type == "base")
             return Base();
     if(type == "da")
             return DerivedA();
     if(type == "db")
             return DerivedB();
     throw std::runtime_error("Error: class type not found.");
}

>> Base rbase = factoryFunctionValue("base")
(Base &) @0x7f9882578038
>> rbase.getType()
(std::string) "Base"
>> rbase.showType()
Class type = Base
>> 

>> Base rda = factoryFunctionValue("da")
 [TRACE]/home/archbox/root-scripts/polymorphism1.C:13  Base copy constructor invoked.
(Base &) @0x7f9882578040

// Fails! It should return derivedA 
>> rda.getType()
(std::string) "Base"
>> rda.showType()
Class type = Base
>> 

A workaround to the object slicing problem could be returning by reference. However, this approach doesn't work because the function factoryFunctionRef returns a reference to the object p allocated on the stack, which is destroyed when the function returns, thus any attempt to use this returned reference to a destroyed object will result in a segmentation fault.

 // This factory functon will fail due to object slicing 
Base& factoryFunctionRef(const std::string& type){
     Base p;
     if(type == "base")
        p = Base();
     else if(type == "da")
        p = DerivedA();
     else if(type == "db")
        p = DerivedB();
     else throw std::runtime_error("Error: class type not found.");
     return p;
}

>> Base& retda2 = factoryFunctionRef("da");
 [TRACE]/home/archbox/root-scripts/polymorphism1.C:19  Base copy assignment operator invoked.
>> retda2.getType()

 *** Break *** segmentation violation

An alternative approach is to return a pointer an object allocated on the heap.

Base* factoryFunctionRawPointer(const std::string& type){
      if(type == "base")
         return new Base();
      else if(type == "da")
         return new DerivedA();
      if(type == "db")
         return new DerivedB();
      return nullptr;
}

// Instantiate of class Base
//--------------------------------
>> Base* ptr_base1 = factoryFunctionRawPointer("base")
(Base *) 0x2564130
>> ptr_base1->getType()
(std::string) "Base"
>> ptr_base1->getType2()
(std::string) "Base"
>> ptr_base1->showType()
Class type = Base
>> 
// 

// Every heap-allocated object must be disposed when no longer 
// needed by calling the operator delete. 
>> delete ptr_base1 ;
// It is advisable to set the pointer of disposed heap-allocated
// object to null.
>> ptr_base1 = nullptr;
>> 

// Instantiate of class DerivedA 
//--------------------------------
>> Base* ptr_da = factoryFunctionRawPointer("da")
(Base *) 0x334e4c0

>> ptr_da->getType()
(std::string) "DerivedA"

>> ptr_da->getType2()
(std::string) "Base"

>> ptr_da->showType()
Class type = DerivedA
>> 

>> delete ptr_da;
>> ptr_da = nullptr;
>> 

Safer Factory Function with smart pointers

The factory function factoryFunctionRawPointer is vulnerable to memory leaks since it returns a raw pointer to a heap-allocated object which needs to be disposed with the operator delete once the object is no longer needed. However, it is easy to forget to call delete and to track all possible return paths and objects needing this pointer. A solution to this issue is to use smart pointers which are stack-allocated objects that delete pointed objects when the the they go out of scope. It is possible to use unique_ptr which is only movable and not copiable or shared_ptr which is copiable.

// Requires header: <memory>
std::unique_ptr<Base> 
factoryFunctionSafe(const std::string& type){
    if(type == "base")
       return std::make_unique<Base>();
    else if(type == "da")
       return std::make_unique<DerivedA>();
    if(type == "db")
       return std::make_unique<DerivedB>();
    return nullptr;
}

Running:

>> auto uptr_base = factoryFunctionSafe("base");

>> uptr_base
(std::unique_ptr<Base, std::default_delete<Base> > &) @0x7f9882578088

>> uptr_base->getType()
(std::string) "Base"

>> uptr_base->showType()
Class type = Base


>> auto uptr_da = factoryFunctionSafe("da");

>> uptr_da->getType()
(std::string) "DerivedA"

>> uptr_da->getType2()
(std::string) "Base"

>> uptr_da->showType()
Class type = DerivedA

STL Containers and Polymorphic Types

  • Storing by value => Leads to object slicing.
// NOTE: Objects base, da and db are copied to the vector, not stored in the vector.
>>  std::vector<Base> objlist = {base, da, db};
 [TRACE] polymorphism1.C:13  Base copy constructor invoked.
 [TRACE] polymorphism1.C:13  Base copy constructor invoked.
 [TRACE] polymorphism1.C:13  Base copy constructor invoked.
 [TRACE] polymorphism1.C:13  Base copy constructor invoked.
 [TRACE] polymorphism1.C:13  Base copy constructor invoked.
 [TRACE] polymorphism1.C:13  Base copy constructor invoked.

 >> objlist[0].getType()
 (std::string) "Base"
 // Expected DerivedA 
 >> objlist[1].getType()
 (std::string) "Base"
 // Expected DerivedB
 >> objlist[2].getType()
 (std::string) "Base"
 >> 

 // Objects stored in the vector don't have 
 // the same memory location as the following objects.
 >> &objlist[0] == &base
 (bool) false
 >> &objlist[1] == &da
 (bool) false
 >> &objlist[2] == &db
 (bool) false
 >> 
  • Storing by Pointer => Object Slicing does not happen.
>> auto objects = std::deque<Base*>{}
(std::deque<Base *, std::allocator<Base *> > &) {}
>> objects.push_back(&base)
>> objects.push_back(&da)
>> objects.push_back(&db)

>> for(const auto& e : objects) { std::cout << "type = " << e->getType() << "\n";}
type = Base
type = DerivedA
type = DerivedB
>> 

>> 
>> objects[0]->getType()
(std::string) "Base"
>> objects[0]->showType()
Class type = Base

>> objects[1]->getType()
(std::string) "DerivedA"
>> objects[1]->showType()
Class type = DerivedA

>> objects[2]->getType()
(std::string) "DerivedB"
>> objects[2]->showType()
Class type = DerivedB
>> 
  • Storing by Reference (Since C++11 with reference wrapper) => Object Slicing does not happen.
#include <functional> // Provides: std::reference_wrapper

// DOES NOT WORK: Ugly, cryptic and verbose  error!! 
>> std::vector<Base&> objRef;
  18: error: 'pointer' declared as a pointer to a reference of type 'Base &'
       typedef _Tp*       pointer;

// Header: 
>> std::vector<std::reference_wrapper<Base>> objRef;
>> auto objRefList = std::vector<std::reference_wrapper<Base>>{};

>> objRefList.push_back(base)
>> objRefList.push_back(da)
>> objRefList.push_back(db)

>> objRefList[0].get()
(Base &) @0x7f3604aa6010

>> objRefList[0].get().getType()
(std::string) "Base"

>> objRefList[1].get().getType()
(std::string) "DerivedA"

>> objRefList[2].get().getType()
(std::string) "DerivedB"
>> 

>> for(const auto& p: objRefList){ std::cout << "type = " << p.get().getType() << "\n";}
type = Base
type = DerivedA
type = DerivedB
>> 

// Check whether objects stored in the vector were not copied.
>> &objRefList[0].get() == &base
(bool) true
>> &objRefList[1].get() == &da
(bool) true
>> &objRefList[2].get() == &db
(bool) true
>> 
  • Storing objects heap-allocated objects (aka free store or dynamic memory)
    • Requires: C++11 + header <memory> which provides smart pointers.
    • See: <memory>
    • In order to avoid memory leaks, it is better to store heap-allocated objects in STL containers using smart pointers as they delete the allocated objects when out of scope or an exception happens (RAII). If raw pointers were used, the heap allocated-objects would not be disposed automatically without the delete operator, as a result, a memory leak could happen.
    • Note: this example assumes single ownership of the allocated objects, in other words, only the container is responsible for deleting the objects. So, shared_ptr is a better option when multiple ownership is required,
#include <functional>
#include <memory>
// Custom deleter type alias 
using Deleter = std::function<void (Base*)>;

// Type synonym for unique_ptr which manages objects 
// from class Base and its classes.
//-----------------------------------
// To use without the custom delter: 
// using Ubase = std::unique_ptr<Base>;
using Ubase = std::unique_ptr<Base, Deleter>;

// Custom deleter used for logging 
// and demonstrate what the smart pointer does.
auto deleter = [](Base* b){
  std::cerr << " [TRACE] Deleted  object of type = " << b->getType() << "\n";
  delete b;
};

auto xlist = std::deque<Ubase>{};
xlist.emplace_back(new Base, deleter)
xlist.emplace_back(new DerivedA, deleter)
xlist.emplace_back(new DerivedB, deleter)

>> xlist[0]->getType()
(std::string) "Base"

>> xlist[1]->getType()
(std::string) "DerivedA"

>> xlist[2]->getType()
(std::string) "DerivedB"
>> 

>> for(int i = 0; i < xlist.size(); i++) { std::cout << "type = " << xlist[i]->getType() << "\n";}
type = Base
type = DerivedA
type = DerivedB
>> 

>> xlist.clear()
 [TRACE] Deleted  object of type = Base
 [TRACE] Deleted  object of type = DerivedA
 [TRACE] Deleted  object of type = DerivedB
>> 

>> xlist.size()
(unsigned long) 0
>> 

Summary:

  • An object loses its polymorphic abilities if assigned by value, passed by value or returned by value. It is called object slicing.
  • Polymorphic types cannot be stored by value in STL containers due to object slicing. The only way to store them is by pointer or reference.
  • In C++, polymorphism only works when objects are assigned by pointer or reference or when passed by pointer or reference.
    • Polymorphism (dynamic) only works with references or pointers.
  • Methods, aka member functions not annotated as virtual cannot be overridden by derived classes.
  • The only way to return polymorphic objects from functions is to return pointers (prone to memory leaks) or smart-pointers (safer) to heap-allocated objects.

1.18.3 Dynamic Polymorphism example - code

File: polymorphism.cpp

#include <iostream>
#include <string>
#include <memory>   // C++11 Smart pointers (unique_ptr and shared_ptr)
#include <map>      // Hash table, hash map or dictionary
#include <vector>
#include <deque>

#include <cassert> // assertions tests 

// Interface class 
class GenericDBDriver{
public:
  GenericDBDriver() = default;
  virtual auto driverName()  const -> std::string {
    return "generic";   
  }
  virtual auto getID() const -> std::string {
    return "unknown";
  }
  virtual auto connect(std::string url) -> void {
    std::cout << "Connecting to generic driver url = " << url << std::endl;
  }
  virtual auto isConnected()  const -> bool {
    return false;
  }
  // Default destructor
  virtual ~GenericDBDriver() {
    std::cout << "Disconnecting from generic driver - id = " << "unknown"<< std::endl;
  }
};

class DriverSQLiteDriver: public GenericDBDriver{
private:
  bool m_connected = false;
  std::string m_id = "unknown";
public:
  DriverSQLiteDriver() = default;
  DriverSQLiteDriver(const std::string& name): m_id(name){}

  // Const keyword in this case indicates that the member function (method)
  // doesn't change the current object.
  auto driverName() const -> std::string override{
    return "sqlite";
  }
  auto getID() const -> std::string override {
    return m_id;
  }  
  // This keyword override -> indicates visually that the method
  // is being overriden and also provides some safety features which
  // helps to improve type-safety and compile-time correctness.
  // For instance, if member function (aka method) being overriden
  // is not annotated as virtual in the base class, this C++11 keyword
  // will generate a compile-time error. It also will generate a compile-time
  // error if the member function being overriden doesn't exist in the base class.  
  auto connect(std::string url) -> void override {
    std::cout << "Connecting to " << url << std::endl;
    m_connected = true;
  }
  auto isConnected() const -> bool override {
    return m_connected;
  }
  ~DriverSQLiteDriver(){
    std::cout << "Disconnecting from PosgresDB - m_id = " << m_id << std::endl;
  }
};


class DriverPostgresSQL: public GenericDBDriver{
private:
  bool m_connected = false;
  std::string m_id = "unknown";  
public:
  // DriverPostgresSQL () = default;
  DriverPostgresSQL(const std::string& name): m_id(name){}

  auto driverName() const -> std::string override{
    return "PostGresSQL";
  }
  virtual auto getID() const -> std::string override {
    return m_id;
  }   
  auto connect(std::string url) -> void override {
    std::cout << "Connecting to " << url << std::endl;
    m_connected = true;
  }
  auto isConnected() const -> bool override {
    return m_connected;
  }
  ~DriverPostgresSQL(){
    std::cout << "Disconnecting from PostGresSQL - id = " << m_id << std::endl;
  }  
};


class DriverSQLServer : public GenericDBDriver{
private:
  bool m_connected = false;
  std::string m_id = "unknown";  
public:
  DriverSQLServer() = default;
  DriverSQLServer(const std::string& name): m_id(name){}

  auto driverName() const -> std::string override{
    return "SQLServer";
  }
  auto getID() const -> std::string override {
    return m_id;
  }    
  auto connect(std::string url) -> void override {
    std::cout << "Connecting to " << url << std::endl;
    m_connected = true;
  }
  auto isConnected() const -> bool override {
    return m_connected;
  }
  ~DriverSQLServer(){
    std::cout << "Disconnecting from SQLServer - id = " << m_id << std::endl;
  }  
};


// Non-polymorphi function -> Parameters passed by value cannot be polymorphic
// void showDriverStatus(const GenericDBDriver driver){ ... }

// Polymorphic function using references 
void showDriverStatus1(const GenericDBDriver& driver){
  std::cout << std::boolalpha;
  std::cout << " [INFO] Driver = " << driver.driverName()
            << " id = " << driver.getID()
            << " ; status = " << driver.isConnected() <<  std::endl;
}

// Polymorphic function using pointers 
void showDriverStatus2(const GenericDBDriver* driver){
  std::cout << std::boolalpha;
  std::cout << " [INFO] Driver = " << driver->driverName()
            << " id = " << driver->getID()
            << " ; status = " << driver->isConnected() <<  std::endl;
}


// Factory method
enum class DriverType{
 generic,
 sqlite,
 postgres,
 sqlserver                        
};

auto dbDriverFactory(const DriverType& dbtype, const std::string& id = "unknown")
  -> std::shared_ptr<GenericDBDriver>{
  if(dbtype == DriverType::generic)
    return std::make_shared<GenericDBDriver>();   
  if(dbtype == DriverType::sqlite)
    return std::make_shared<DriverSQLiteDriver>(id);   
  if(dbtype == DriverType::postgres)
    return std::make_shared<DriverPostgresSQL>(id);   
  if(dbtype == DriverType::sqlserver)
    return std::make_shared<DriverSQLServer>(id);    
  // Failure -> DO NOT return old C++98 NULL as it is not typesafe 
  return nullptr;
}


class DriverFactory{
private:

public:

  DriverFactory() = delete;
  DriverFactory(const DriverFactory& lhs) = delete;

  static auto getInstance() 
};

int main(){

  {
    std::cout << "\n====== Test 1 ===========" << std::endl;
    // Uniform initialization 
    GenericDBDriver   d0;
    DriverSQLServer   d1{"d1"};
    DriverPostgresSQL d2 = {"d2"};
    DriverSQLServer   d3 = {"d3"};

    showDriverStatus1(d0);
    showDriverStatus1(d1);
    d1.connect("file:///home/user/datbase.sqlite");
    showDriverStatus1(d1);
    showDriverStatus1(d2);
    showDriverStatus1(d3);

    std::cout << "====== End of test 1 ===========" << std::endl;
  }

  {
    std::cout << "\n====== Test 2 ===========" << std::endl;
    // Uniform initialization 
    GenericDBDriver   d0;
    DriverSQLServer   d1{"d1"};
    DriverPostgresSQL d2 = {"d2"};
    DriverSQLServer   d3 = {"d3"};

    showDriverStatus2(&d0);
    showDriverStatus2(&d1);
    d1.connect("file:///home/user/datbase.sqlite");
    showDriverStatus2(&d1);
    showDriverStatus2(&d2);
    showDriverStatus2(&d3);

    std::cout << "====== End of test 2 ===========" << std::endl;
  }

  { // Failure ! -> It only calls the methods of GenericDBDriver
    // It is not possible to polymorphism (subtyping polymorphism) in C++
    // without pointers or references.
    std::cout << "\n====== Test 3 ===========" << std::endl;
    std::deque<GenericDBDriver> xs;

    xs.push_back(GenericDBDriver{});
    xs.push_back(DriverSQLServer {"d1"});
    xs.push_back(DriverPostgresSQL{"d2"});
    xs.push_back(DriverSQLServer{"d3"});

    for(const auto& x: xs){
      std::cout << " - driver = " << x.driverName() << " ; id = " << x.getID() << std::endl;
    }

    assert(xs.at(0).driverName() == "generic");
    assert(xs.at(1).driverName() == "generic");
    assert(xs.at(2).driverName() == "generic");
    assert(xs.at(3).driverName() == "generic");

    std::cout << "\n====== End of test 3 ===========" << std::endl;
  }


  { std::cout << "\n====== Test 4 ===========" << std::endl;
    std::deque<GenericDBDriver*> xs;

    auto d0 = GenericDBDriver{};
    auto d1 = DriverSQLiteDriver {"d1"};
    auto d2 = DriverPostgresSQL{"d2"};
    auto d3 = DriverSQLServer{"d3"};
    xs.push_back(&d0);
    xs.push_back(&d1);
    xs.push_back(&d2);
    xs.push_back(&d3);

    for(const auto& x: xs){
      std::cout << " - driver = " << x->driverName() << " ; id = " << x->getID() << std::endl;;
    }
    assert(xs.at(0)->driverName() == "generic");
    assert(xs.at(1)->driverName() == "sqlite");
    assert(xs.at(2)->driverName() == "PostGresSQL");
    assert(xs.at(3)->driverName() == "SQLServer");

    std::cout << "====== End of test 4 ===========" << std::endl;
  }  

  { std::cout << "\n====== Test 5 ===========" << std::endl;
    std::deque<std::shared_ptr<GenericDBDriver>> xs;
    xs.push_back(dbDriverFactory(DriverType::generic));
    xs.push_back(dbDriverFactory(DriverType::sqlite,    "d1"));
    xs.push_back(dbDriverFactory(DriverType::postgres,  "d2"));
    xs.push_back(dbDriverFactory(DriverType::sqlserver, "d3"));
    for(const auto& x: xs){
      std::cout << " - driver = " << x->driverName() << " ; id = " << x->getID() << std::endl;;
    }
    assert(xs.at(0)->driverName() == "generic");
    assert(xs.at(1)->driverName() == "sqlite");
    assert(xs.at(2)->driverName() == "PostGresSQL");
    assert(xs.at(3)->driverName() == "SQLServer");

    std::cout << "====== End of test 5 ===========" << std::endl;
  }  
  return EXIT_SUCCESS;
}

Running:

$ clang++ polymorphism.cpp -std=c++1z -Wall -Wextra && ./a.out

====== Test 1 ===========
 [INFO] Driver = generic id = unknown ; status = false
 [INFO] Driver = SQLServer id = d1 ; status = false
Connecting to file:///home/user/datbase.sqlite
 [INFO] Driver = SQLServer id = d1 ; status = true
 [INFO] Driver = PostGresSQL id = d2 ; status = false
 [INFO] Driver = SQLServer id = d3 ; status = false
====== End of test 1 ===========
Disconnecting from SQLServer - id = d3
Disconnecting from generic driver - id = unknown
Disconnecting from PostGresSQL - id = d2
Disconnecting from generic driver - id = unknown
Disconnecting from SQLServer - id = d1
Disconnecting from generic driver - id = unknown
Disconnecting from generic driver - id = unknown

====== Test 2 ===========
 [INFO] Driver = generic id = unknown ; status = false
 [INFO] Driver = SQLServer id = d1 ; status = false
Connecting to file:///home/user/datbase.sqlite
 [INFO] Driver = SQLServer id = d1 ; status = true
 [INFO] Driver = PostGresSQL id = d2 ; status = false
 [INFO] Driver = SQLServer id = d3 ; status = false
====== End of test 2 ===========
Disconnecting from SQLServer - id = d3
Disconnecting from generic driver - id = unknown
Disconnecting from PostGresSQL - id = d2
Disconnecting from generic driver - id = unknown
Disconnecting from SQLServer - id = d1
Disconnecting from generic driver - id = unknown
Disconnecting from generic driver - id = unknown

====== Test 3 ===========
Disconnecting from generic driver - id = unknown
Disconnecting from SQLServer - id = d1
Disconnecting from generic driver - id = unknown
Disconnecting from PostGresSQL - id = d2
Disconnecting from generic driver - id = unknown
Disconnecting from SQLServer - id = d3
Disconnecting from generic driver - id = unknown
 - driver = generic ; id = unknown
 - driver = generic ; id = unknown
 - driver = generic ; id = unknown
 - driver = generic ; id = unknown

====== End of test 3 ===========
Disconnecting from generic driver - id = unknown
Disconnecting from generic driver - id = unknown
Disconnecting from generic driver - id = unknown
Disconnecting from generic driver - id = unknown

====== Test 4 ===========
 - driver = generic ; id = unknown
 - driver = sqlite ; id = d1
 - driver = PostGresSQL ; id = d2
 - driver = SQLServer ; id = d3
====== End of test 4 ===========
Disconnecting from SQLServer - id = d3
Disconnecting from generic driver - id = unknown
Disconnecting from PostGresSQL - id = d2
Disconnecting from generic driver - id = unknown
Disconnecting from PosgresDB - m_id = d1
Disconnecting from generic driver - id = unknown
Disconnecting from generic driver - id = unknown

====== Test 5 ===========
 - driver = generic ; id = unknown
 - driver = sqlite ; id = d1
 - driver = PostGresSQL ; id = d2
 - driver = SQLServer ; id = d3
====== End of test 5 ===========
Disconnecting from generic driver - id = unknown
Disconnecting from PosgresDB - m_id = d1
Disconnecting from generic driver - id = unknown
Disconnecting from PostGresSQL - id = d2
Disconnecting from generic driver - id = unknown
Disconnecting from SQLServer - id = d3
Disconnecting from generic driver - id = unknown

1.18.4 STL containers and polymorphism

C++'s STL (Standard Template Library) containers/collections such as vector, deque, map, stack and so on can only store pointers to instances of different derived classes from a given base class. However the STL containers cannot store references to those instances. The STL std::reference_wrapper solves this problem.

Documentation:

Demonstration in CERN's ROOT C++ REPL:

  • Create test classes.
// Base class 
class IRoot{
public:
  virtual auto getName() const -> std::string {
    return "root";
  }
};

//===== Derived Classes ====== //

class A: public IRoot{
public:
  auto getName() const -> std::string {
    return "Class A";
  }
};

class B: public IRoot{
public:
  auto getName() const -> std::string {
    return "Class B";
  }
};
  • Play with sample objects:
// Create test objects 
auto r = IRoot();
auto a = A();
auto b = B();

// Test objects 
>> r.getName()
(std::string) "root"
>> a.getName()
(std::string) "Class A"
>> b.getName()
(std::string) "Class B"
>>
  • Store those objects in a collection by pointer:
>> auto xsp = std::deque<IRoot*>()
(std::deque<IRoot *, std::allocator<IRoot *> > &) {}
>> xsp.push_back(&r)
>> xsp.push_back(&a)
>> xsp.push_back(&b)
>> xsp
(std::deque<IRoot *, std::allocator<IRoot *> > &) { @0x2f01990, @0x2f01998, @0x2f019a0 }
>>
>>

>> for(auto x: xsp) { std::cout << "name = " << x->getName() << std::endl; }
name = root
name = Class A
name = Class B
>>

  • Try to store those object r, a and b in a container/collection by reference.
/** Many of errors: new_allocator.h:63:18: error: 'pointer' declared
 * as a pointer to a reference of type 'IRoot &' typedef _Tp* pointer;
 */
auto xs = std::deque<IRoot&>() 
  • Try to store those object r, a and b in a container/collection by reference using std::reference_wrapper.
>> auto xsr = std::deque<std::reference_wrapper<IRoot>> {}
(std::deque<std::reference_wrapper<IRoot>, std::allocator<std::reference_wrapper<IRoot> > > &) {}
>>

xsr.push_back(a)
xsr.push_back(r)
xsr.push_back(b)

>> xsr
(std::deque<std::reference_wrapper<IRoot>, std::allocator<std::reference_wrapper<IRoot> > > &)
{ @0x31013c0, @0x31013c8, @0x31013d0 }
>>

>> xsr.at(0).get().getName()
(std::string) "Class A"
>> xsr.at(1).get().getName()
(std::string) "root"
>> xsr.at(2).get().getName()
(std::string) "Class B"
>> 
>>

>> for(auto x: xsr) { std::cout << "name = " << x.get().getName() << std::endl; }
name = Class A
name = root
name = Class B
>> 

>> for(const auto& x: xsr) { std::cout << "name = " << x.get().getName() << std::endl; }
name = Class A
name = root
name = Class B
>> 

References:

1.19 OOP Operator Overloading

1.19.1 Overview

Operator overloads allows defining common primitives types operations for user defined types such as classes. Seom types of operators are arithmentic operators (+, *, /), comparison operators ( ==, !=, <, >, <=, >=), assignment +=, *=, /=, and so on.

Types of operator overloading:

  • Member function operator overloading.
    • The operator such as +, *, / or == is a class method, the left hand-side of a binary operator is the current object passed as implicit argument and the right-hand-side is the operator argument.
  • Free function operator overloading (a non member function, non method):
    • Non-friend function operator overloading - The operator is defined as a free function, just as an ordinary function without access to class private data. There is no implicit argument, both left-hand-side and right-hand-side are function arguments.
    • Friend function Operator overloading - The operator is defined as a free function (non member function or non method) and has access to class private data and private member functions.

Operators that cannot be overloaded:

  • (.) Object.member() - Member access operator
  • (?) Ternary operator
  • (::) Escope Resolution operator

Operator properties:

  • Precedency
  • Associativity
  • Arity - number of operands (operator arguments)
    • Unary Operators: -Object, !Object, …
    • Binary Operators: (+), (=), (*), …
  • Types of operators:
    • Arithmetic: +, /, *,
    • Logical: &&, ||
    • Assignment: =, +=, *=, ..
    • Bitwise: &, |, ^ …
    • Function-call:
      • Object(10), Object.operator()(10) ..
    • Increment/Decrement: Object++, –Object …
    • Pointer
      • Array index, ([]), Object[10]
      • Deference operator (*), *Object
      • Address-of operator (&), &Object

1.19.2 Example: Arithmetic member function operator overloading

Source:

Class Complex declaration (file: Complex.hpp):

/** Complex number */
class Complex{
private:
     double m_real, m_imag;
public:
     /** Make class printable */
     friend std::ostream& operator<<(std::ostream&, const Complex&);    

     /** Constructors */
     Complex();
     Complex(double real, double imag);
     /** Named constructor */
     static Complex fromReal(double real);
     static Complex fromImag(double imag);

     double Real() const;
     double Imag() const;

     Complex operator-();
     Complex operator+(const Complex& rhs);
     Complex operator-(const Complex& rhs);
     Complex operator*(const Complex& rhs);
     Complex operator/(const Complex& rhs);

     Complex operator+(double d);
     Complex operator-(double d);
     Complex operator*(double scale);
     Complex operator/(double factor);
};

Class Complex implementation (file: Complex.cpp):

  • Arithmetic operator overloading definitions for Complex as operator explicit argument:
 Complex Complex::operator-(){
      return {-m_real, -m_imag};
 }

 Complex Complex::operator+(const Complex& rhs)
 {
      const auto& self = *this;
      return {self.m_real + rhs.m_real, self.m_imag + rhs.m_imag };
 }

 Complex Complex::operator-(const Complex& rhs)
 {
      const auto& self = *this;
      // (x1 + j . y1)(x2 + j . y2) = x1.x2 + j( -y1.y2 )
      return Complex{self.m_real - rhs.m_real, self.m_imag - rhs.m_imag };
 }

Complex Complex::operator*(const Complex& rhs)
 {
     return Complex{
         m_real * rhs.m_real - m_imag * rhs.m_imag,
         m_real * rhs.m_imag + m_imag * rhs.m_real
                 };
 }
  • Arithmetic operator overloading definitions for double as explicit argument.
Complex Complex::operator+(double d){
     return Complex{m_real + d, m_imag + d};
}
Complex Complex::operator-(double d){
     return Complex{m_real - d, m_imag - d};
}
Complex Complex::operator*(double scale){
     return {this->m_real * scale, this->m_imag *  scale};
}
Complex Complex::operator/(double factor){
     return Complex(this->m_real / factor, this->m_imag / factor);
}

Test in CERN's ROOT REPL:

Load file and instantiate some variables:

>> .L operator-overload1.cpp 

>> Complex c1;

>> Complex c2{2, 5}
(Complex &) @0x7f4088144020

>> Complex c3{3, 4}
(Complex &) @0x7f4088144030

Test operators:

>> std::cout << "c1 + c2 + c3 = " << (c1 + c2 + c3) << std::endl;
c1 + c2 + c3 = Complex{ 5 + 9j }

>> std::cout << c2 + c3 << std::endl;
Complex{ 5 + 9j }

>> std::cout << c3 + c2 << std::endl;
Complex{ 5 + 9j }

Call operator explicitly:

  • c2 + c3 = c2.operator+()(c3)
  • c3 + c2 = c3.operator+()(c2)
// c2 + c3 = c2.operator+(c3) 
>> std::cout << c2.operator+(c3) << std::endl;
Complex{ 5 + 9j }

// c3 + c2 = c2.operator+(c3)
>> std::cout << c3.operator+(c2) << std::endl;
Complex{ 5 + 9j }

Member function operator overloading is not symmetric:

  • c2 + 10 = c2.operator+(10) => OK.
  • 10 + c2 = 10.operator+(c2) ?? => Compile-time ERROR!
>> std::cout << "c2 + 10 = " << c2 + 10 << std::endl;
c2 + 10 = Complex{ 12 + 15j }
>> 

// ERROR!!!
> std::cout << "10 + c2 = " << 10 + c2 << std::endl;
ROOT_prompt_17:1:33: error: invalid operands to binary expression ('int' and 'Complex')
std::cout << "10 + c2 = " << 10 + c2 << std::endl;
                             ~~ ^ ~~
/home/archbox/opt/root/include/TString.h:531:1: note: candidate function not viable: no known conversion from 'Complex' to
      'const TString' for 2nd argument
operator+(T i, const TString &s)
^

1.19.3 Example: Arithmetic free-function function operator overloading

Operators can also be defined as ordinary functions (aka free functions), or non-member and non-friend function, it means without access to private members.

Source:

Syntax for non-friend free-function operator overloading:

  • Unary operator
RETURN_TYPE operator <<UNARY-OPERATOR>>(const LSH& lhs);
// Or, C++11 syntax 
auto operator <<UNARY-OPERATOR>>(const LSH& lhs) -> RETURN_TYPE;

// Example: Invert vector 
Vector3D operator-(const Vector3D& lhs);
auto operator-(const Vector3D& lhs) -> Vector3D;
  • Binary operator
RETURN_TYPE operator <<BINARY-OPERATOR>>(const LSH& lhs, const RHS& rhs);
// Or, C++11 flavor: 
auto operator <<BINARY-OPERATOR>>(const LSH& lhs, const RHS& rhs) -> RETURN_TYPE;

// Example: 
Vector3D operator+(const Vector3D& lhs, double rhs);
auto operator+(const Vector3D& lhs, double rhs) -> Vector3D;

Declaration (It should be Vector2D.hpp)

/** A struct is just a class with everything public by default. */
struct Vector2D{
    double x, y;
    Vector2D();
    Vector2D(double x, double y);
};

std::ostream& operator<<(std::ostream& os, const Vector2D& vec);
Vector2D operator+ (const Vector2D& lhs, const Vector2D& rhs);

/** C++11 Notation */
auto operator- (Vector2D const& lhs, Vector2D const& rhs) -> Vector2D;
auto operator-(const Vector2D& lhs) -> Vector2D;

Vector2D operator*(double k, const Vector2D& rhs);
Vector2D operator*(const Vector2D& rhs, double k);

auto operator/(const Vector2D& lhs, double k) -> Vector2D;

Implementation (It should be Vector2D.cpp)

  • Constructors:
Vector2D::Vector2D(): x(0.0), y(0.0)
{
}

Vector2D::Vector2D(double x, double y): x(x), y(y)
{
}
  • Operators as free-functions:
std::ostream& operator<<(std::ostream& os, const Vector2D& vec)
{
    return os << "Vector2D{ x = " << vec.x << " ; "
              << " y = " << vec.y << " } ";
}

Vector2D operator+ (const Vector2D& lhs, const Vector2D& rhs)
{
    return Vector2D(lhs.x + rhs.x, lhs.y + rhs.y);
}

auto operator- (Vector2D const& lhs, Vector2D const& rhs) -> Vector2D
{
    return Vector2D(lhs.x - rhs.x, lhs.y - rhs.y);
}
  ... .... ... ... 

// Symmetric operator 

Vector2D operator*(const Vector2D& rhs, double k)
{
    return {k * rhs.x, k * rhs.y};
}

auto operator/(const Vector2D& lhs, double k) -> Vector2D
{
   return {lhs.x / k, lhs.y / k};
}

REPL Test:

Load file in the CERN's ROOT REPL.

>> .L operator-overload2.cpp

>> Vector2D v1{3, 5};
>> Vector2D v2{5, 6};

>> std::cout << "v1 = " << v1 << std::endl;
v1 = Vector2D{ x = 3 ;  y = 5 } 
>> std::cout << "v2 = " << v2 << std::endl;
v2 = Vector2D{ x = 5 ;  y = 6 } 

Test operator overloading:

>> auto v = v1 + v2
(Vector2D &) @0x7fb99c26d030

>> v.x
(double) 8.0000000

>> v.y
(double) 11.000000

>> std::cout << "v1 + v2 = " << v << std::endl;
v1 + v2 = Vector2D{ x = 8 ;  y = 11 } 

Test symmetry:

>> std::cout << " v1 * 5 = " << v1 * 5 << std::endl;
 v1 * 5 = Vector2D{ x = 15 ;  y = 25 } 

>> std::cout << " 5 * v1 = " <<  5 * v1 << std::endl;
 5 * v1 = Vector2D{ x = 15 ;  y = 25 } 

Explicitly calling operator function:

>> std::cout << " v1 * 5 = " <<  operator*(v1, 5) << std::endl;
 v1 * 5 = Vector2D{ x = 15 ;  y = 25 } 

>> std::cout << " 5 * v1 = " <<  operator*(5, v1) << std::endl;
 5 * v1 = Vector2D{ x = 15 ;  y = 25 } 

>> std::cout << " v1 + v2 = " <<  v1 + v2 << std::endl;
 v1 + v2 = Vector2D{ x = 8 ;  y = 11 } 

>> std::cout << " v1 + v2 = " << operator+(v1, v2) << std::endl;
 v1 + v2 = Vector2D{ x = 8 ;  y = 11 } 

1.19.4 Example: Arithmetic friend function operator overloading

Operators can also be defined as class friend-functions, in this case the opertor is just a ordinary function, however with access to class privage member variable and memebr functions.

Source:

Class declaration: (It should be in header file Vector2D.hpp)

class Vector2D{
private:
   double m_x, m_y;
public:
   Vector2D();
   Vector2D(double x, double y);
   double X() const;
   double Y() const;    
   friend std::ostream& operator<<(std::ostream& os, const Vector2D& vec);
   friend Vector2D operator+ (const Vector2D& lhs, const Vector2D& rhs);

   /** C++11 Notation */
   friend auto operator- (Vector2D const& lhs, Vector2D const& rhs) -> Vector2D;
   friend auto operator-(const Vector2D& lhs) -> Vector2D;

   friend Vector2D operator*(double k, const Vector2D& rhs);
   friend Vector2D operator*(const Vector2D& rhs, double k);
   friend auto operator/(const Vector2D& lhs, double k) -> Vector2D;
};

Class implementation: (It should be in header file Vector2D.cpp)

  • Constructors and member functions:
Vector2D::Vector2D(): m_x(0.0), m_y(0.0)
{
}

Vector2D::Vector2D(double x, double y): m_x(x), m_y(y)
{
}
double Vector2D::X() const {
    return this->m_x;
}

double Vector2D::Y() const {
    return this->m_y;
}
  • Some class operators functions:
    • Note: They are not member functions (methods).
std::ostream& operator<<(std::ostream& os, const Vector2D& vec)
{
     return os << "Vector2D{ x = " << vec.m_x << " ; "
               << " y = " << vec.m_y << " } ";
}

Vector2D operator+ (const Vector2D& lhs, const Vector2D& rhs)
{
     return Vector2D(lhs.m_x + rhs.m_x, lhs.m_y + rhs.m_y);
}

auto operator- (Vector2D const& lhs, Vector2D const& rhs) -> Vector2D
{
     return Vector2D(lhs.m_x - rhs.m_x, lhs.m_y - rhs.m_y);
}

    . .. ... ... .. 

Vector2D operator*(double k, const Vector2D& rhs)
{
     return {k * rhs.m_x, k * rhs.m_y};
}

Vector2D operator*(const Vector2D& rhs, double k)
{
     return {k * rhs.m_x, k * rhs.m_y};
}

REPL Testing:

>> .L operator-overloading3.cpp 

>> Vector2D v1{3, 5};
>> v1.X()
(double) 3.0000000

>> v1.Y()
(double) 5.0000000

>> (v1 * 4).X()
(double) 12.000000

>> (v1 * 4).Y()
(double) 20.000000

>> Vector2D v2{5, 6}
(Vector2D &) @0x7f181a05b020

>> v = v1 + v2
(Vector2D &) @0x7f181a05b030

>> v.X()
(double) 8.0000000

>> v.Y()
(double) 11.000000

1.19.5 Example: Insertion << and extraction >> operator overload

Moved to: STL IO - Input and Output advanced IO.

1.19.6 Member function operator overloading reference card

Member functions operators are binary or unary class methods and the left hand-side of a binary operator is its implicit argument.

Arithmetic Operators

Table 3: Arithmetic member function operator overloading
Name Operator Member function signature
    for class C and RHS type R
Addition LHS + RHS C C::operator+(const RHS& rhs)
Subtraction LHS - RHS C C::operator-(const RHS& rhs)
Multiplication LHS * RHS C C::operator*(const RHS& rhs)
Division LSH / RHS C C::operator/(const RHS& rhs)
Remainder LSH % RHS C C::operator%(const RHS& rhs)
  • C: Class that the operator belongs to.
  • LHS - Left-hand-side type (Implicit argument current object of class C.)
  • RHS - Right-hand-side type or class (a class is also a type)

Comparison Operators

Table 4: Comparison member function operator overloading
Name Operator Member function signature
    for class C and RHS type R
Equal LHS == RHS bool C::operator==(const RHS& rhs)
Not equal LHS != RHS bool C::operator-(const RHS& rhs)
Greater than LHS > RHS bool C::operator>(const RHS& rhs)
Greater or equal than LHS >= RHS bool C::operator>=(const RHS& rhs)
Less than LHS < RHS bool C::operator<(const RHS& rhs)
Less or equal than LHS <= RHS bool C::operator<=(const RHS& rhs)

Prefix/Postfix - Increment / Decrement

Table 5: Increment and decrement operator overloading.
Name Operator Expansion Member function signature
Prefix increment ++Object Object = Object + 1 C& C::operator++()
Postfix increment Object++ Object = Object + 1 C C::operator++(int)
       
Prefix decrement –Object Object = Object - 1 C& C::operator–()
Postfix decrement Object-- Object = Object - 1 C C::operator–(int)
  • Note: The argument is postfix operator is a dummy parameter, it is unnused. The point of this parameter is just allow the compiler differ between prefix and postfix operators.
  • Prefix operator: The increment or decrement happens before the operattion being performed on the variable.
    • Example: For an int variable set to 10, the statement printVar(++x), will print 11, the increment happens before the operation, then the value of x will be 11.
  • Postfix operator: The increment or decrement happens after the operation being performed on the variable.
    • Example: For an int variable set to 10, the statement printVar(x++), will print 10, the increment happens after the operation, then the value of x will be 11.
  • Semantics Summary: (Note <- array means assignment)
    • Prefix:
      • x = ++y; Means y <- y + 1 and x <- y
      • x = –y; Means y <- y - 1 and x <- y
    • Postfix:
      • x = y++; Means x <- y and y <- y + 1
      • x = y–; Means x <- y and y <- y - 1

Assignment Operators

Table 6: Assignment operator overloading
Name Operator Expansion Member function signature
      for class C and RHS type R
Assignment LHS = RSH   C& C::operator= (const RHS& rhs)
Copy-assignment operator LHS = RSH   C& C::operator= (const C& rhs)
       
Assignment sum LHS += RHS LHS = LHS + RHS C C::operator+=(const RHS& rhs)
Assignment subtraction (aka difference) LHS -= RHS LHS = LHS - RHS C C::operator=-(const RHS& rhs)
Assignment product (multiplication) LHS *= RHS LHS = LHS * RHS C C::operator*=(const RHS& rhs)
Assignment division LSH /= RHS LHS = LHS / RHS C C::operator/=(const RHS& rhs)
Assignment remainder LHS %= RHS LHS = LHS % RHS C C::operator%=(const RHS& rhs)
       
Assignment bitwise AND LHS &= RHS LHS = LHS & RHS C C::operator&=(const RHS& rhs)
Assignment bitwise OR LHS |= RHS LHS = LHS | RHS C C::operator|=(const RHS& rhs)
Assignment bitwise exclusive OR (XOR) LHS ^= RHS LHS = LHS ^ RHS C C::operator^=(const RHS& rhs)
Assignment left shift LHS <<= RHS LHS = LHS << RHS C C::operator<<=(const RHS& rhs)
Assignment right shift LHS >>= RHS LHS = LHS >> RHS C C::operator>>=(const RHS& rhs)
       

Function-Call Operators

Function-call operators allow objects to be called as they were functions and define "functors" - function-objects.

  • Note: The operator is '()' parenthesis.
Table 7: Function call operator overloading - "functors"
Operator Usage Member function signature for class C and RHS type R
RHS x = Object() R C::operator()()
int a = Object("hello world") int C::operator()(const char* text)
std::string text = AnObject(count) std::string C::operator()(std::size_t count)
RHS result = Object(x, y, text) RHS C::operator() (int x, double y, char const* text)

Pointer Operarators

Table 8: Pointer operator overloading.
Name Operator Usage Member function signature
      for class C and RHS type R
Subscript, array index [] Object[Index] X C::operator[] (const Index& index)
Indirection - deference * *Object X& C::operator*()
Indirection - deference * *Object X& C::operator*() const
Class member access or dereference operator -> pointerToOjbect->Member() X* C::operator->()
Class member access or dereference operator -> pointerToOjbect->Member() X* C::operator->() const
Address or reference & &Object X* C::operator&()
Heap allocation - single object new C* ptr = new C;  
Heap allocation - array new [] C* ptr = new C[10];  
Delete operator delete delete ptr;  
Delete array operator delete [] delete [] ptr;  
  • Note:
    • The overloading of operators (*) and (->) is useful for implementing smart pointers.
    • The overloading of operators new and delete can be used for implementing custom allocators.

1.19.7 Free function operator overloading tables reference card

Note: The following operators cannot be overloaded by non-member functions, in other words, they cannot be free functions.

  • = - assignment operator
  • [] - array index
  • () - Function-call operator
  • -> - Deference operator

Arithmetic Operators

Table 9: Free-function arithmetic operator overloading
Name Operator Operator function signature
Addition LHS + RHS LHS operator+(const LHS&, const RHS& rhs)
Subtraction LHS - RHS LHS operator-(const LHS&, const RHS& rhs)
Multiplication LHS * RHS LHS operator*(const LHS&, const RHS& rhs)
Division LSH / RHS LHS operator/(const LHS&, const RHS& rhs)
Remainder LSH % RHS LHS operator%(const LHS&, const RHS& rhs)
  • C: Class that the operator belongs to.
  • LHS - Left-hand-side type
  • RHS - Right-hand-side type or class (a class is also a type)

Comparison Operators

Table 10: Free-function comparison operator overloading
Name Operator Operator function signature
Equal LHS == RHS bool operator==(const LHS&, const RHS& rhs)
Not equal LHS != RHS bool operator-(const LHS&, const RHS& rhs)
Greater than LHS > RHS bool operator>(const LHS&, const RHS& rhs)
Greater or equal than LHS >= RHS bool operator>=(const LHS&, const RHS& rhs)
Less than LHS < RHS bool operator<(const LHS&, const RHS& rhs)
Less or equal than LHS <= RHS bool operator<=(const LHS&, const RHS& rhs)

Prefix/Postfix - Increment / Decrement

Table 11: Free-function prefix and postfix increment/decrement operator overloading
Name Operator Expansion Operator function signature
Prefix increment ++Object Object = Object + 1 LHS& operator++(LHS& lhs)
Postfix increment Object++ Object = Object + 1 LHS operator++(LSH& lhs, int dummy)
       
Prefix decrement –Object Object = Object - 1 LHS& operator–(LHS& lhs)
Postfix decrement Object-- Object = Object - 1 LHS operator–(LHS&,int dummy)

Insertion / Extraction operators

Table 12: Free-function stream insertion/extraction operator overloading
Name Operator Operator function signature
Insertion operator (<<) OS << Object; std::ostream& operator<<(std::ostream& os, const RHS& rhs)
Insertion operator (<<) [C++11] OS << Object; auto operator<<(std::ostream& os, const RHS& rhs) -> std::ostream&
Extraction operator (>>) IS >> Object; std::istream& operator>>(std::istream& os, RHS& rhs)
Extraction operator (>>) [C++11] IS >> Object; auto operator>>(std::istream& os, RHS& rhs) -> std::istream&
  • OS: Output stream: derived class of std::ostream (std::cout, std::cerr, std::stringstream …)
  • IS: Inoput stream: derived clas sof std::istream (std::cin, std::ifstream, std::stringstream …)
  • Object: An instance of the class or type RHS (Right-hand side).
  • Insertion operator (<<)
    • Insert or write object to output stream.
  • Extraction operator (>>)
    • Extract or read object from input stream.

Calling the operator (<<) explicitly:

>> std::cout << " [1] Hello world C++" << std::endl;
 [1] Hello world C++

>> operator<<(std::cout, " [2] Hello world C++\n");
 [2] Hello world C++

>> std::cout << " [3] Hello world C++" << "\n [3] This is a new line.\n";
 [3] Hello world C++
 [3] This is a new line.
>> 

>> operator<<(operator<<(std::cout, " [3] Hello world C++"), "\n [3] This is a new line.\n");
 [3] Hello world C++
 [3] This is a new line.

 template<class T> std::ostream& printStream(std::ostream& os, T const& object){
     return os << object;
 }

 >> printStream(std::cout, "hello world\n");
 hello world

 >> printStream(printStream(std::cout, "hello world\n"), " A new line\n");
 hello world
  A new line

1.20 Lambda Expressions

1.20.1 Overview

Lambda expressions, also known as lambda functions or lambda abstractions, are ubiquitous in functional programming languages such as Haskell, OCaml, Scala and Scheme. They were by introduced in a theoretical way by Alonzo Church in the lambda calculus. Lisp was first programming language to use lambda functions and now this function programming feature has made its way into mainstream and has arrived to C# (Cshap), Java 8 and C++11.

In C++, Lambda functions are not ordinary functions, actually, they are special function-objects or "C++ functors". Lambda abstractions can be passed as arguments to any function; returned from functions; can have state and also they can be defined locally at the call-site simplifying all the boilerplate code necessary to pass a function to callbacks, event handlers and higher order functions.

Summary:

  • Lambda functions were introduced in C++11. Before C++11 lambdas were available as Boost.Lambda.
  • Lambda functions can be:
    • returned from functions.
    • be passed as function parameters.
    • Can be stored in data structures.
    • Hold state and capture variables (closure).
    • Non-capturing lambdas can be converted to function pointers what is useful with old C-APIs.
  • Lambda turns C++11 is a game changer and turns C++ into an quasi-functional programming language.
  • Practical Use Cases:
    • Callbacks
    • Higher order functions
    • Simplify design patterns
    • Asynchronous code
    • Create functions at runtime.

Main parts

  • Capture list between square brackets [ ]
  • Argument list between parenthesis ()
  • Function body between curly braces {}

Syntax:

[capture](parameters) -> return-type {body}

Some lambda functions:

auto fn = [](int n){ return n * 10; }

// Test in CERN's ROOT REPL
>> auto fn = [](int n){ return n * 10; }
((lambda) &) @0x7f5e578f2010
>> 

>> auto fn = [](int n){ return n * 10; }
((lambda) &) @0x7f5e578f2010
>> 

Parts of a C++'s lambda expression: - (Microsoft Inc. Lambda C++)

  1. Capture clause - Specifies captured variables (closure).
    • [ ] - Empty capture clause means that no variable from escope is captured.
    • [x, y]- The variables x and y are captured by value and the the lambda body will get a copy of x and y. They cannot be modified in the body of the lambda expression.
    • [&x , y] - The variable x is captured by reference and y by value, therefore x can be modified in the body of the lambda expression and y cannot.
    • [&] - Capture all variables used in the lambda's body by reference.
    • [=]- Captures all variables used in the body by value (copy). The variables cannot be modifed as they are passed by value.
    • [=, &blob]- The variable blob is captured by reference and all other variables are captured by value (copy).
    • [this] - Captures the "this" pointer of the enclosing class.
  2. Parameter list (Optional)- Parameters, aka arguments, of the lambda expression.
  3. Mutable specification (optional)
  4. exception-specification (optional)
  5. trailing return type (optional)
  6. lambda-body - The body can access variables such as:
    • Lambda parameters passed between parenthesis: (int x)
    • Captured variables from the enclosing scope.
    • Global variables.
    • Class data members if the lambda expression is defined inside a class.
--
            (1)  (2)    (3)       (4)         (5)
               |    |         |            |           |
              ...  ....  .....+....  ...+....     ......
              [ ]  ( )  mutable  throw()  -> int
              {                                                   
                // (6)                                              
                // Lambda body 
                int n = x + y;
                x = y;
                y  = n;
                return n;
              }

1.20.2 TODO Basic Examples

Proof-of-concept code

Code Highlights:

  • Lambda function - lambda1
// Example (1)
auto lambda1 = [](const std::string& str){
   std::cout << "Lambda1: : I got the value = " << str << "\n";
};

// Alternative 1:
//------------------------------
std::function<void (const std::string&)> lambda1A = [](const std::string& str) {
  std::cout << "Lambda1: : I got the value = " << str << endl;
};

// Alternative 2:
//------------------------------
std::function<auto (const std::string&) -> void> lambda1B = [](const std::string& str) {
  std::cout << "Lambda1: : I got the value = " << str << endl;
};

// Alternative 3:
//------------------------------
// Type synonym 
using FnAction = std::function<void (const std::string&)>;
// Alternative type synonym: 
using FnAction = std::function<auto (const std::string&) -> void>;

FnAction lambda1C = [](const std::string& str) {
  std::cout << "Lambda1: : I got the value = " << str << endl;
};

>> lambda1("hello world")
Lambda1: : I got the value = hello world

>> lambda1("hola mundo")
Lambda1: : I got the value = hola mundo

>> lambda1A("testing C++")
Lambda1: : I got the value = testing C++

>> lambda1B("Running lambda1B")
Lambda1: : I got the value = Running lambda1B

>> lambda1C("I am lambda1C function")
Lambda1: : I got the value = I am lambda1C function
>>  
  • Higher order function which returns a lambda function which takes two ints as parameters returning an int. This function captures the parameter m by value, it means that the parameter is copied in the function body.
// As it is in the code.
//----------------------------------------- 
std::function<int (int, int)> makeFunction1(int m){
    return [m](int x, int y){ return m * (x + y); };
}

>> auto fn = makeFunction1(4)
(std::function<int (int, int)> &) @0x7fb73017f010
>> fn(3, 5)
(int) 32
>> fn(4, 3)
(int) 28
>> 

// Alternative 1:
//-----------------------------------------
auto makeFunction1A(int m) -> std::function<int (int, int)>{
    return [m](int x, int y){ return m * (x + y); };
}

>> fna(3, 5)
(int) 32
>> fna(4, 3)
(int) 28
>> auto fna6 = makeFunction1A(6)
(std::function<int (int, int)> &) @0x7f696d823030
>> fna6(3, 5)
(int) 48
>> fna6(4, 3)
(int) 42
>> 

// Alternative 2:
//-----------------------------------------
auto makeFunction1B(int m) -> std::function<auto (int, int) -> int>{
    return [m](int x, int y){ return m * (x + y); };
}

// Alternative 3:
//-----------------------------------------
// C++11 Type synonym
using BinaryIntFunctionA = std::function<int (int, int)>;
using BinaryIntFunctionB = std::function<auto (int, int) -> int>;
// Prior to C++11 type synonym
typedef std::function<auto (int, int) -> int> BinaryIntFunction;

auto makeFunction1C(int m) -> BinaryIntFunctionB {
    return [m](int x, int y){ return m * (x + y); };
}

>> auto fnc = makeFunction1C(8)
(std::function<int (int, int)> &) @0x7f696d823050
>> fnc(3, 5)
(int) 64
>> fnc(6, 7)
(int) 104
>> 8 * (6 + 7)
(int) 104
>> 

Complete program output (file:src/lambdaFun.cpp) :

g++ lamdaFun.cpp -o bin/lamdaFun.bin && bin/lamdaFun.bin

-----------------------------------------------------------------------------------------
>> Example(1) - Testing function lambda1
Lambda1: : I got the value = Hello
Lambda1: : I got the value = World

-----------------------------------------------------------------------------------------
>> Example(2) Testing function lambda2
Lambda 2 :: I got the value  = Japan
Lambda 2 :: I got the value  = Korea

-----------------------------------------------------------------------------------------
>> Example(3) Testing function lambda3
   - Lambda function can be defined and executed at the call-site
Lambda 3 :: I got the value = C++11 is awesome!

-----------------------------------------------------------------------------------------
>> Example(4) Testing function sumLambda
   - Lambda functions  can return values as any function.
sumLambda(10.0, 25.34) = 35.34
sumLambda(-10.23, 4.56) = -5.67

-----------------------------------------------------------------------------------------
>> Example(5) Testing function lamdaCapture
   - Lambda functions  can capture its environment (closures) and have state as "function objects"
  x  = 5  c = 3
lamdaCapture(2, 5)  = 32
  x  = 5  c = 4
  lamdaCapture(1, 2) =  16
  x  = 5  c = 5

-----------------------------------------------------------------------------------------
>> Example(6) Testing function sumLambda
   - Lambda functions  can play well with STL algorithms
Print all vector elements - Version 1
  v[0] = 1
  v[1] = 2
  v[2] = 3
  v[3] = 4
  v[4] = 5
  v[5] = 6
  v[6] = 7
  v[7] = 8
Print all vector element Version 2  - local state with 'static' keyword
  v[0] = 1
  v[1] = 2
  v[2] = 3
  v[3] = 4
  v[4] = 5
  v[5] = 6
  v[6] = 7
  v[7] = 8

-----------------------------------------------------------------------------------------
>> Example(7) 
   Playing with STL transform algorithm.
  Vector transformed =  
v  [0] = 4
v  [1] = 7
v  [2] = 10
v  [3] = 13
v  [4] = 16
v  [5] = 19
v  [6] = 22
v  [7] = 25

-----------------------------------------------------------------------------------------
>> Example(8) 
   Lambda functions can be returned from functions and be generated at run-time.
  mulSumBy2(2, 4) = 12
  mulSumBy2(3, 1) = 8
  mulSumBy5(2, 4) = 30
  mulSumBy5(9, 2) = 55

-----------------------------------------------------------------------------------------
>> Example(9) 
   Lambda functions can be returned from functions and have state.
  Running dummy function
    (*) m = 2, n = 0, x = 1, y = 3, z = 8
  Running dummy function
    (*) m = 2, n = 1, x = 2, y = 5, z = 15
  Running dummy function
    (*) m = 3, n = 0, x = 1, y = 3, z = 12
  Running dummy function
    (*) m = 3, n = 1, x = 2, y = 5, z = 22

-----------------------------------------------------------------------------------------
>> Example(10) 
   Lambda functions can return lambda functions!!
  addTo5(4) = 9
  addTo5(3) = 8
  addTo10(4) = 14
  addTo10(3) = 13

-----------------------------------------------------------------------------------------
>> Example(11) 
   Lambda functions can be passed as function arguments!
foldVector(dataset2, 0, add) = 28
foldVector(dataset2, 1, mul) = 5040

-----------------------------------------------------------------------------------------
>> Example(12) - Observer pattern
(observer 1) Temperature changed to 30.5 C
(observer 2) Sensor temperature changed to 30.5 C
(observer 1) Temperature changed to 20.5 C
(observer 2) Sensor temperature changed to 20.5 C

References

1.20.3 Lambdas in Deep

Define two test functions:

>> auto add_10 = [](int x){ return x + 10; };
>> auto mul_3  = [](int x){ return x * 3; };

>> add_10(5)
(int) 15

>> mul_3(5)
(int) 15

The lambda objects add_10 and mul_3 can be called like functors (callable objects) overloading the function-call operator.

>> add_10.operator()(5)
(int) 15

>> mul_3.operator()(5)
(int) 15
>> 

Define two functions fun_add_10 and fun_mul_3;

>> int fun_add_10(int n){ return n + 10; }
>> int fun_mul_3(int n){ return n * 3; }

>> fun_add_10(2)
(int) 12

>> fun_add_10(5)
(int) 15

>> fun_mul_3(5)
(int) 15

Both functions have the same type and can be reffered by the same function-pointer:

>> if(typeid(fun_add_10) == typeid(fun_mul_3)) puts(" => Function have same type"); 
=> Function have same type

>> int (* funptr)(int) = nullptr;

>> funptr = fun_add_10;
>> funptr(4)
(int) 14

>> funptr = fun_mul_3;
>> funptr(4)
(int) 12

Lambdas does not have the same type:

>> if(typeid(add_10) != typeid(mul_3)) puts(" => Lambdas do not have the same type");
 => Lambdas do not have the same type

>> std::cout << std::boolalpha;
>> std::cout << "value = " << (typeid(add_10) != typeid(mul_3)) << std::endl;
value = true

Lambdas cannot be referred by the same pointer.

// Take the address of the object add_10 
>> auto ptr = &add_10;
>> ptr
((lambda) *) @0x7ffed74b7368

// Apply to a value 
>> (*ptr)(4)
(int) 14
>> 
>> ptr->operator()(4)
(int) 14

// FAILURE!!! because both lambdas do not have the same type.  Attempt
// to assing the address of mul_3 to the pointer to add_10.
>> ptr = &mul_3;
ROOT_prompt_76:1:7: error: assigning to '(lambda at ROOT_prompt_0:1:15) *' from incompatible type '(lambda
      at ROOT_prompt_2:1:15) *'
ptr = &mul_3;
      ^~~~~~

Both objects are add_10 and mul_3 are instance of different anonymous classes created by the compiler which overloads the function-call operator member function.

The lambda object add_10 is equivalent to:

class _anonymous_lambda_class1
{
public:
     int operator()(int n) const { return n + 10; }
};

_anonymous_lambda_class1 add_10; 

The lambda object mul_3 is equivalent to:

class _anonymous_lambda_class2
{
public:
     int operator()(int n) const { return n * 3; }
};

_anonymous_lambda_class2 mul_3; 

The following capturing lambda is equivalent to:

// ----------- Capturing lambda -----------------// 
double x = 10; 
std::string name = "Somebody";

auto capture_lambda = [x, &name] (int n, const char* word) -> void 
{
   printf(" n = %d ; word = %s, x = %f ; name = %s", n, word, x, name.c_str());
};

//--- The capturing lambda (closure) is the same as --------// 
// 
class _anonymous_lambda_class
{
   double _x;
   std::string& _name;  
public:     
  _anonymous_lambda_class(double x, std::string& name): 
     _x(x)
   , _name(name) 
  { 
  }   

  void operator()(int n, const char* word) const 
  {
     printf(" n = %d ; word = %s, x = %f ; name = %s", n, word, x, name.c_str());
  }

} capture_lambda(x, name);

Passing lambdas as function parameters:

Both lambda objects can be passed by using templated function like in the following code. The function apply_function, accepts any function pointer with type int (*) (int), any callable object (functor) that takes an integer and returns an integer and also lambdas that are also callable objects.

template<typename TFunc> void apply_function(TFunc func, int n){    
    for(int i = 0; i < n; ++i){
       printf(" i = %d\n", func(i));
    }
}

Passing function pointers as parameter to the templated function:

>> apply_function(fun_add_10, 3)
 i = 10
 i = 11
 i = 12

>> apply_function(&fun_add_10, 4)
 i = 10
 i = 11
 i = 12
 i = 13

>> apply_function(fun_mul_3, 3)
 i = 0
 i = 3
 i = 6

Passing lambdas, aka lambda objects as parameters to the templated function:

// Pass lambda object add_10 
>> apply_function(add_10, 3)
 i = 10
 i = 11
 i = 12

// Pass lambda object mul_3 as parameter 
>> apply_function(mul_3, 3)
 i = 0
 i = 3
 i = 6

>> apply_function([](int x){ return 5 * x; }, 3)
 i = 0
 i = 5
 i = 10  

Passign a functor to the templated function:

struct AFunctor{
   int operator()(int x) { return 4 * x + 5; }
};

>> afc(4)
(int) 21

>> afc.operator()(4)
(int) 21
>> 

>> apply_function(afc, 3)
 i = 5
 i = 9
 i = 13

>> apply_function(AFunctor(), 3)
 i = 5
 i = 9
 i = 13

As a result:

  • C++ lambdas expressions are not functions, as in most functional languages such as Haskell, OCaml and so on. They are functors or callable-objects instances of anonymous classes generated by the compiler.
  • Lambda expressions with the same type parameters and same return type do not have the same type and cannot be assigned to the same variable or referred by the same pointer.
  • Lambdas can be passed as parameters by using templated functions. In this case lambdas and functors are better than passing function pointers as the both are most likely to be inlined by the compiler removing the function-call overhead.
  • The only way to store lambdas in the same container or in an std::vector container is by using type-erasure or type-erasure containers such as std::function.
  • std::function<int (int)> is not the type of any of those lambda functions.

1.20.4 Stateful lambdas

The function-call operator member function operator()(args) of a lambda object is annotated with const, therefore it cannot modify captured parameters by value.

// Compile-time error!! 
auto incrementer = [n = 1]() { return ++n; };

The previous line is equivalent to:

// The previous line is the same as: 
class __Anonymous_lambda_classA {
    int n = 1;
public: 
    __Anonymous_lambda_class() = default;

    // Function-call operator annotated as const 
    int operator()() const 
    {  
       // Value n cannot be modified!! 
       // Compiel-time error!!
       return ++n; 
    }
};

__Anonymous_lambda_classA incrementer;

The mutable keyword allows variables captured by value to be modified in the lambda expression.

// Now it works
auto incrementer = [n = 1]() mutable { return ++n; };

// Could also be written as: 
auto incrementer = [n = 1]() mutable -> int 
                  {  
                     return ++n; 
                  };

// Could also be written as: 
auto incrementer = [n = 1](void) mutable -> int 
                   { 
                     return ++n; 
                   };

The previous lambda expression 'incrementer' is equivalent to:

// The previous line is the same as: 
class __Anonymous_lambda_classB {
    int n = 1;
public: 
    __Anonymous_lambda_class() = default;

    // Function-call operator non cost 
    int operator()()  
    {  
       return ++n; 
    }
};

__Anonymous_lambda_classB incrementer;

Test in Cling REPL:

$ ~/opt/cling_2018-09-16_fedora27/bin/cling -std=c++1z
// Now it works
auto incrementer = [n = 1]() mutable { return ++n; };
auto incrementerB = incrementer;

[cling]$ incrementer()
(int) 2
[cling]$ incrementer()
(int) 3
[cling]$ incrementer()
(int) 4
[cling]$ incrementer()
(int) 5

[cling]$ incrementerB()
(int) 2
[cling]$ incrementerB()
(int) 3
[cling]$ incrementerB()
(int) 4

[cling]$ inc()
(int) 6
[cling]$ inc()
(int) 7
[cling]$ inc()
(int) 8
[cling]$ incrementer()
(int) 6
[cling]$ 

Example: stateful lambda X captured variable

Sample code: main-stateful-lambda.cpp

#include <iostream>

int main()
{
    int n = 10;
    auto func = [n]() mutable { n = n + 1; return n; };

    std::cout << " func() = " << func() << std::endl;
    std::cout << " func() = " << func() << std::endl;
    std::cout << " func() = " << func() << std::endl;
    std::cout << " n = " << n << std::endl;
    return 0;
}

Output:

func() = 11
func() = 12
func() = 13
n = 10

1.20.5 Generic lambdas C++14

  1. Overview

    C++14 introduced generic-lambdas language feature which allows defining lambda with auto keyword which behaves like templated functions.

    Papers:

    Generic lambda expression named 'print_lambda' with generic parameter a.

    auto print_lambda = [](const auto& a){ std::cout << " Value of a = " << a << '\n'; };
    
    // Or equivalent: 
    auto print_lambda = [](auto const& a){ std::cout << " Value of a = " << a << '\n'; };
    

    The previous lamba is an anonymous Functor (function-object) created by the compiler with a templated parameter in the function-call operator.

    // Anonymous functor 
    class __Anonymous_generic_lambda
    {
    public: 
       template<typename T>
       void operator(const T& a)
       {  
           std::cout << " Value of a = " << a << '\n';
       }
    };
    
  2. Example 1 - Basic Generic Lambdas

    File: cpp-generic-lambda.cpp

    #include <iostream>
    #include <vector>
    #include <string>
    #include <algorithm>
    
    template<typename iterable, typename Callable>
    void custom_algorithm(iterable const& seq, Callable func)
    {
        for(auto const& x: seq) func(x);
    }
    
    int main(int argc, char** argv)
    {
        auto print_lambda = [](auto a){ std::cout << " Value of a = " << a << '\n'; };
    
        std::puts(" ---------- Apply to single argument ---------------");
        print_lambda(10);
        print_lambda('x');
        print_lambda("<generic-lambda-on-the-rocks!>");
    
        std::puts("\n ---- Apply to integer vector / st::for_each ------------");
        std::vector<int> xs{10, 2, 5, 9};
        std::for_each(xs.begin(), xs.end(), print_lambda);
    
        std::puts("\n -- [1] Apply to string vector / std::for_each ------------");
        std::vector<std::string> words = {"C++",  "ADA", "RUST", "DLang", "Dart" };
        std::for_each(words.begin(), words.end(), print_lambda);
    
        std::puts("\n -- [2] Apply to string vector / std::for_each ------------");
    
        int i = 0;
        // Capturing lambda defined at call-site.
        std::for_each(words.begin(), words.end(),
                      [&i](auto const& x){
                            std::cout << " x[" << i++ << "] = " << x << '\n';
                      });
    
        std::puts("\n - Generic lambda with custom algorithm ----------------"