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 ----------------");
        custom_algorithm(xs, print_lambda);
    
       return 0;
    }
    

    Building:

    $ g++  cpp-generic-lambda.cpp -o app1.bin -std=c++1z -Wall -ggdb
    

    Running:

    $ ./app1.bin 
     ---------- Apply to single argument ---------------
     Value of a = 10
     Value of a = x
     Value of a = <generic-lambda-on-the-rocks!>
    
     ---- Apply to integer vector / st::for_each ------------
     Value of a = 10
     Value of a = 2
     Value of a = 5
     Value of a = 9
    
     -- [1] Apply to string vector / std::for_each ------------
     Value of a = C++
     Value of a = ADA
     Value of a = RUST
     Value of a = DLang
     Value of a = Dart
    
     -- [2] Apply to string vector / std::for_each ------------
     x[0] = C++
     x[1] = ADA
     x[2] = RUST
     x[3] = DLang
     x[4] = Dart
    
     - Generic lambda with custom algorithm ----------------
     Value of a = 10
     Value of a = 2
     Value of a = 5
     Value of a = 9
    
    
  3. Example 2 - Generic lambdas and templates

    Generic lambdas work in a similar way to templated functions or classes. A lambda generic expression can be applied to any type satisfying its type requirements (concepts). In the following code, the generic lambda mygen_lambda1 which is similar to the functor functor1, can be applied to any class or type with members get_param and get_name regardless of its class hierarchy. It works in a similar way to duck typing of dynamic languages such as Python, Ruby, JavaScript and so on. The difference for dynamic typed languages is that, if the type does not satisfy the requirements of the generic lambda, a compile-time error will happen.

    File: generic-lambda2.cpp

    #include <iostream>
    #include <vector>
    #include <string>
    #include <algorithm>
    
    struct Circle
    {
        int rad = 0;
    
        std::string get_name(){ return "circle"; }
    
        void set_parameter(int rad)
        {
            std::printf("[INFO] Circle parameter set to %d \n", rad);
            this->rad = rad;
        }
        int get_param() const {return rad; }
    };
    
    class Gearbox
    {
        int x = 10;
     public:
        std::string get_name(){ return "gearbox"; }
    
        void set_parameter(int x)
        {
            std::printf("[INFO] Gearbox parameter set to %d \n", x);
            this->x = x;
        }
    
        int get_param() const {return x; }
    
        void dummy_non_common_method() { }
    };
    
    // The generic lambda mygen_lambda1 is similar to this
    // templated functor.
    struct functor1
    {
        template<typename T>
        void operator()(T& obj, int k)
        {
            std::cout << " Object name = " << obj.get_name() << "\n";
            obj.set_parameter(k);
            std::cout << " Parameter set to " << k << "\n";
        }
    };
    
    
    int main(int argc, char** argv)
    {
        auto mygen_lambda1 = [](auto& obj, int k){
            std::cout << " Object name = " << obj.get_name() << "\n";
            obj.set_parameter(k);
            std::cout << " Parameter set to " << k << "\n";
        };
    
        std::puts("\n --- Apply lambda on Circle object ");
        Circle c1;
        mygen_lambda1(c1, 5);
    
        std::puts("\n --- Apply lambda on Gearbox object ");
        Gearbox g1;
        mygen_lambda1(g1, 9);
    
        std::puts("\n --- Apply functor on Gearbox object ");
        functor1 fun1;
        fun1(g1, 9);
    
            return 0;
    }
    
    

    Program output:

     --- Apply lambda on Circle object 
     Object name = circle
    [INFO] Circle parameter set to 5 
     Parameter set to 5
    
     --- Apply lambda on Gearbox object 
     Object name = gearbox
    [INFO] Gearbox parameter set to 9 
     Parameter set to 9
    
     --- Apply functor on Gearbox object 
     Object name = gearbox
    [INFO] Gearbox parameter set to 9 
     Parameter set to 9
    
    

1.20.6 Self-executable lambda for complex initialization

Self-executable lambdas functions can also be used for performing complex local or global static variables initialization.

Example 1: Initialization of global variables.

#include <iostream>
#include <cstdio>

class AClass{
private:
    double x, y, z; 
public:
    AClass(): x(0), y(0), z(0) { }
    ~AClass(){
       std::printf("Object deleted\n");
    }
    void setX(double x){ this->x = x; }
    void setY(double y){ this->y = y; }
    void setZ(double z){ this->z = z; }
    void show(){
       std::printf("AClass{ x = %.3f ; y = %.3f ; z = %.3f}\n", x, y, z);
    }   
};

AClass globalObject =
    [](){
        AClass cls;
        cls.setX(10.0);
        cls.setY(20.0);
        cls.setZ(15.0);
        cls.show();
        return cls;
    }();

int main(){
    std::cout << "Program Initialized" << std::endl;
    globalObject.show();
    std::cout << "Program end" << std::endl;
    return 0;
}

Output:

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

AClass{ x = 10.000 ; y = 20.000 ; z = 15.000}
Program Initialized
AClass{ x = 10.000 ; y = 20.000 ; z = 15.000}
Program end
Object deleted

Example 2: Initialization of function static variables.

#include <iostream>
#include <string>
#include <map>

int getDayOfWeekNum2(const std::string& weekDay){
    // Type alias / synonym 
    using WeekDayTable = std::map<std::string, int>;
    static WeekDayTable week_table =
             [](){ // Lambda function
                std::cerr << " [LOG] Initializing month table" << "\n";
                WeekDayTable tbl;
                tbl["Sun"] = 1;
                tbl["Mon"] = 2;
                tbl["Tue"] = 3;
                tbl["Wed"] = 4;
                tbl["Thu"] = 5;
                tbl["Fri"] = 6;
                tbl["Sat"] = 7;
                std::cerr << " [LOG] Month table initialization end. OK." << "\n";
                return tbl;
        }();
    if(week_table.find(weekDay) != week_table.end())
       return week_table[weekDay];  
    return -1;
}
int main(){
    std::cout << "getDayOfWeekNum2(Mon)   = " << getDayOfWeekNum2("Mon")   << "\n";
    std::cout << "getDayOfWeekNum2(Tue)   = " << getDayOfWeekNum2("Tue")   << "\n";
    std::cout << "getDayOfWeekNum2(Sat)   = " << getDayOfWeekNum2("Sat")   << "\n";
    std::cout << "getDayOfWeekNum2(ERROR) = " << getDayOfWeekNum2("ERROR") << "\n";
    return 0;
};

Output stderr (Program logging)

[LOG] Initializing month table
[LOG] Month table initialization end. OK.

Output stdout (Program output)

getDayOfWeekNum2(Mon)   = 2
getDayOfWeekNum2(Tue)   = 3
getDayOfWeekNum2(Sat)   = 7
getDayOfWeekNum2(ERROR) = -1

1.20.7 Universal function adapter - std::bind

  1. Overview

    The operator std::bind can simplify help simplifying turning ordinary functions, instance methods (member functions) and static methods (static member functions) into ordinary functions.

    Example 1: Turn a function of signature:

    • Input Signature:
      • double functionABC(double a, double c, double c)
    • Target signature:
      • function<double (double)>

    For the function functionABC

    double functionABC(double a, double c, double c);
    

    The expression:

    // Type of fnFunOfA is std::function<double (double)>
    auto fnFunOfA = std::bind(functionABC, _1, 10.0, 20.0)
    // It is the same as 
    auto fnFunOfA = [](double x){ return functionABC(x, 10.0, 20.0); };
    // It also equivalent to 
    std::function<double (double)> fnFunOfA = [](double x){ return functionABC(x, 10.0, 20.0); };
    

    So, it follows that:

    auto fnFunOfB = std::bind(functionABC, 25.0 , _1, 20.0);
    // It is equivalent to 
    auto fnFunOfB = [](double x){ return (functionABC, 25.0 , _1, 20.0); };
    

    By using more lambda placeholders it is also possible to generate multi variable functions:

    #include <functional> 
    using namespace std::placeholders; // Import placeholders, _1, _2, _3 ...  
    
    //----------------------------------//
    auto fnFunOfAB = std::bind(functionABC, _1 , _2, 20.0);
    // Equivalent to 
    auto fnFunOfAB = [](double x, double y){ return functionABC( x , y, 20.0);};
    
    //----------------------------------//
    auto fnFunOfAC = std::bind(functionABC, _1 , 10.0, _2);
    // Equivalent to 
    auto fnFunOfAC = [](double x, double y){ return functionABC( x , 10.0, y);};
    
    //----------------------------------//
    auto fnFunOfABC = std::bind(functionABC, _1 , 10.0, _2);
    // Equivalent to 
    auto fnFunOfABC = [](double x, double y, double z){ return functionABC( x , y, z);};
    

    Binding non-static methods (member function)

    For the following class:

    struct FunctionObject{
         double x;
         double y;
         FunctionObject(double x, double y): x(x), y(y) {};      
         double operator ()(double a){   
            return a * a;
         }   
         double operator ()(double a, double b, double c){
             return a * x + b * y + c / (x + y);
         }
         double method1(double a){
             return this->x * a  + this->y / a;
         }
         double method2(double a, double b, double c){
             return  c * (a / x + b / y);
         }       
     };
    

    It is possible to create lambda functions from those class methods (member functions):

    FunctionObject obj;
    
    //-------------------------
    auto method1LambdaA = std::bind(&FunctionObject::method1, fobj, _1);
    // Equivalent to: 
    auto method1LambdaA = [&obj](double a){return obj.method1(x); };
    
    //-------------------------
    auto method2LambdaAsFnOfAC = std::bind(&FunctionObject::method2, &fobj, _1, 10.0, _2);
    // Equivalent to: 
    auto method2LambdaAsFnOfAC = [&obj](double a, double c){ return obj.method2(a, 10.0, c); }
    
    //-------------------------
    auto functionOfObj = std::bind(&FunctionObject::method1, _1, 10.0);
    // Equivalent to: 
    auto functionOfobj = [](FunctionObject a){ return obj.method1(obj, 10.0);};
    
  2. std::bind Example

    File:

    Running:

    $ g++ lambda-bind.cpp -o lambda-bind.bin -g -std=c++11 -Wall -Wextra -ldl 
    $ ./lambda-bind.bin
    
    ======== Test 1 ========
    lambda-bind.cpp:58: ; sum10(2.0) = 12
    lambda-bind.cpp:59: ; sum10(4.5) = 14.5
    lambda-bind.cpp:60: ; sum10(25.0) = 35
    ======== Test 2 ========
    lambda-bind.cpp:66: ; vectorLenAsFunctionOfX(4.0) = 27.2213
    lambda-bind.cpp:67: ; std::bind(vectorLength, _1, 10.0, 25.0)(4.0) = 27.2213
    lambda-bind.cpp:68: ; vectorLenAsFunctionOfX(10.0) = 28.7228
    lambda-bind.cpp:69: ; std::bind(vectorLength, _1, 10.0, 25.0)(10.0) = 28.7228
    Tabulating - vectorLenAsFunctionOfX
         1.000    26.944
         2.000    27.000
         3.000    27.092
         4.000    27.221
         5.000    27.386
    ======== Test 3 ========
    lambda-bind.cpp:77: ; vectorLenAsFunctionOfY(14.0) = 30.348
    lambda-bind.cpp:78: ; vectorLenAsFunctionOfY(20.0) = 33.541
    Tabulating - vectorLenAsFunctionOfY
         1.000    26.944
         2.000    27.000
         3.000    27.092
         4.000    27.221
         5.000    27.386
    ======== Test 4 ========
    lambda-bind.cpp:92: ; vectorLenAsFunctionOfYZ(3.0, 6.0) = 12.0416
    lambda-bind.cpp:93: ; vectorLenAsFunctionOfYZ(15.0, 26.0) = 31.6386
    ======== Test 5 ========
    lambda-bind.cpp:97: ; fobj(4.0) = 16
    lambda-bind.cpp:98: ; fobj(5.0) = 25
    lambda-bind.cpp:99: ; fobj(10.0) = 100
    lambda-bind.cpp:101: ; fobj(4.0, 10.0, 5.0) = 104.357
    lambda-bind.cpp:102: ; fobj(6.0, 8.0, 9.0) = 100.643
    Running: tabulate(0.0, 5.0, 1.0, fobj)
         0.000     0.000
         1.000     1.000
         2.000     4.000
         3.000     9.000
         4.000    16.000
         5.000    25.000
    Turning class member function into lambda function 
     Note: it is not possible (0.0, 5.0, 1.0, fobj.method1)
    lambda-bind.cpp:111: ; fobj.method1(10.0) = 60.8
    lambda-bind.cpp:112: ; method1LambdaA(10.0) = 60.8
    lambda-bind.cpp:113: ; fobj.method1(20.0) = 120.4
    lambda-bind.cpp:114: ; method1LambdaA(20.0) = 120.4
    lambda-bind.cpp:115: ; fobj.method1(30.0) = 180.267
    lambda-bind.cpp:116: ; method1LambdaA(30.0) = 180.267
    Tabulating method1LambdaA
         0.000       inf
         1.000    14.000
         2.000    16.000
         3.000    20.667
         4.000    26.000
         5.000    31.600
    Tabulating method1LambdaA using direct lambda
         0.000       inf
         1.000    14.000
         2.000    16.000
         3.000    20.667
         4.000    26.000
         5.000    31.600
    ======== Test 6 ========
    lambda-bind.cpp:129: ; method2LambdaAsFnOfA(5.0) = 41.6667
    lambda-bind.cpp:130: ; method2LambdaAsFnOfA(6.0) = 45
    lambda-bind.cpp:131: ; method2LambdaAsFnOfA(10.0) = 58.3333
    lambda-bind.cpp:136: ; fobj.method2(3.0, 10.0, 4.0) = 7
    lambda-bind.cpp:137: ; method2LambdaAsFnOfAC(3.0, 4.0) = 7
    lambda-bind.cpp:138: ; fobj.method2(15.0, 10.0, 14.0) = 52.5
    lambda-bind.cpp:139: ; method2LambdaAsFnOfAC(15.0, 14.0) = 52.5
    
    
    

1.21 Functor Function-Object and higher order functions

Functor is any object which behaves like a function and callable like a function. Unlike C++ ordinary functions, functors can have internal state and change its internal data as well.

Functors are implementing in C++ by overloading the function application operator.

The code in the file: file:src/cpp-functor.cpp shows an exhaustive example about how to implement, use functors and implement client code using dynamic polymorphism (aka subtyping or inheritance), static polymorphism (aka template metaprogramming) and C++11's function type std::function.

Example:

class IMathFunctor {
public:
        // Pure virtual function
        // => const -> Means that the function cannot change the object internal state.
        // => (= 0) -> Means abstract member function or abstract method.
        virtual double operator()(double x) const = 0;
        virtual ~IMathFunctor() = default;
};
  • Linear Function "C++ functor." - function-object
/** Linear Function "C++ functor." - function-object 
 *   LinFun(x) = A * x + B
 */
class LinFun: public IMathFunctor {
private:
        // Linear coefficient or line slope 
        double _a; 
        double _b;
public:
        LinFun(double a, double b): _a(a), _b(b){}
        auto getA()             -> double   { return _a;}
        auto setA(double a)         -> void     { _a = a; }
        auto getB()             -> double   { return _b;}
        auto setB(double b)         -> void     { _b = b; }

        // Function-call operator => Makes this object callable
        //------------------------------
        // double operator()(double x)  -> double   { return _a * x + _b;}  
        double operator()(double x) const { return _a * x + _b;}    
};

Usage:

// Function linear object - modelling a linear function 3 * x + 4.0 
LinFun fun1(3.0, 4.0);
std::cout << "a = " << fun1.getA() << " ; b = " << fun1.getB() << nl;
std::cout << "fun1(3.0) = " << fun1(3.0) << nl;  
std::cout << "fun1(4.0) = " << fun1(4.0) << nl;

Output:

a = 3 ; b = 4
fun1(3.0) = 13
fun1(4.0) = 16
fun1(5.0) = 19

Higher order functions (functions that calls or return functions) can be implemented using dynamic polymorphis or inheritance; static polymorphism, also known as template metaprogramming and using the type std::function from C++11.

  • Higher order function using dynamic polymorphism (inheritance). This function only accepts implementations of IMathFunctor and cannot worth with an arbitrary function-object, ordinary functions or C++11 lambda function. Another drawback is the runtime overhead of virtual function-calls.

Example:

void tabulateDynamic(const IMathFunctor& fun, double start, double stop, double step){
        std::cout << std::fixed << std::setprecision(3);
        for(double x = start; x <= stop ; x += step)
                std::cout << std::setw(10) << x << std::setw(10) << fun(x) << "\n";
}

Usage:

tabulatDynamic(fun1, 0.0, 5.0, 1.0);

Sample Output:

-----> Tabulating fun1
    0.000     4.000
    1.000     7.000
    2.000    10.000
    3.000    13.000
    4.000    16.000
    5.000    19.000
  • Higher order function using static polymorphism - The advantage of this function is that it can work with any callable object like functors (function-objects), ordinary functions and C++11 lambda functions. Another benefit is the lower runtime overhead than the implementation using dynamic polymorphism. For this case, the runtime cost of dynamic polymorphism is not significant, however it can become noticeable on large scale computation or high performance computations.
  • Example:
template<class Function>
void tabulateStatic(const Function& fun, double start, double stop, double step){
        std::cout << std::fixed << std::setprecision(3);
        for(double x = start; x <= stop ; x += step)
                std::cout << std::setw(10) << x << std::setw(10) << fun(x) << "\n";
}

Usage:

std::cout << " -----> Tabulating fun1" << nl;
tabulateStatic(fun1, 0.0, 5.0, 1.0);
std::cout << " -----> Tabulating fun2" << nl;
tabulateStatic(fun2, 0.0, 5.0, 1.0);
  • Higher order function using the C++11 type std::function - The type std::function provides type erasure and can work with any functor, ordinary function and C++11 lambda functions, in addition it also allows all those types of functions to be stored in STL containers.

Example:

void tabulateLambdaList(
      const std::vector<std::function<double (double)>> funlist
     ,double start
     ,double stop
     ,double step
     ){
     std::cout << std::fixed << std::setprecision(3);
     for(double x = start; x <= stop ; x += step){
             std::cout << std::setw(10) << x;
             // const auto& is used for avoid uncessary copies 
             for(const auto& fun: funlist)
                     std::cout << std::setw(10) << fun(x);
             std::cout << "\n";
     }
}

Usage:

tabulateLambdaList({fun1, fun2, exp, ordinaryFunction}, 0.0, 5.0, 1.0)

Output:

  x         fun1     fun2      exp      ordinaryFunction
0.000     4.000     4.000     1.000     0.000
1.000     7.000     9.000     2.718     3.000
2.000    10.000    18.000     7.389     6.000
3.000    13.000    31.000    20.086     9.000
4.000    16.000    48.000    54.598    12.000
5.000    19.000    69.000   148.413    15.000

Compilation of file:src/cpp-functor.cpp

$ clang++ cpp-functor.cpp -o cpp-functor.bin -g -std=c++11 -Wall -Wextra &&

Complete program output of file:src/cpp-functor.cpp

./cpp-functor.bin

a = 3 ; b = 4
fun1(3.0) = 13
fun1(4.0) = 16
fun1(5.0) = 19
=======================
a = 2 ; b = 3 ; c = 4
fun2(3.0) = 31
fun2(4.0) = 48
fun2(5.0) = 69
======= [1] Client Code using dynamic polymorphism  ================
 -----> Tabulating fun1
     0.000     4.000
     1.000     7.000
     2.000    10.000
     3.000    13.000
     4.000    16.000
     5.000    19.000
 -----> Tabulating fun2
     0.000     4.000
     1.000     9.000
     2.000    18.000
     3.000    31.000
     4.000    48.000
     5.000    69.000
======= [2] Client Code using dynamic polymorphism  ================
     0.000     4.000     4.000
     1.000     7.000     9.000
     2.000    10.000    18.000
     3.000    13.000    31.000
     4.000    16.000    48.000
     5.000    19.000    69.000
======= Client Code using static polymorphism (template)  ================
 -----> Tabulating fun1
     0.000     4.000
     1.000     7.000
     2.000    10.000
     3.000    13.000
     4.000    16.000
     5.000    19.000
 -----> Tabulating fun2
     0.000     4.000
     1.000     9.000
     2.000    18.000
     3.000    31.000
     4.000    48.000
     5.000    69.000
 -----> Tabulating lambda function f(x) = x * x
     0.000     0.000
     1.000     1.000
     2.000     4.000
     3.000     9.000
     4.000    16.000
     5.000    25.000
 -----> Tabulating ordinary function f(x) = 3 * x
     0.000     0.000
     1.000     3.000
     2.000     6.000
     3.000     9.000
     4.000    12.000
     5.000    15.000
 -----> Tabulating ordinary function f(x) = exp(x)
     0.000     1.000
     1.000     2.718
     2.000     7.389
     3.000    20.086
     4.000    54.598
     5.000   148.413
======= Client Code using C++11 lambda std::function  ================
     0.000     4.000     4.000     1.000     0.000
     1.000     7.000     9.000     2.718     3.000
     2.000    10.000    18.000     7.389     6.000
     3.000    13.000    31.000    20.086     9.000
     4.000    16.000    48.000    54.598    12.000
     5.000    19.000    69.000   148.413    15.000

1.22 TODO OOP Multiple Inheritance

1.22.1 Ambiguity resolution

This example shows how to solve multiple inheritacne ambiguity problems in C++ when a derived class inherits more than two base classes containing member variables and member functions with same name.

File: file:src/inheritance-multiple-ambiguity.cpp

  • Base class BaseA:
class BaseA{
protected:
     std::string key = "KEY-BASEA";
public: 

     std::string _name;

     BaseA(std::string name): _name(name) {
        std::cerr << " [LOG] BaseA instantiated. OK." << "\n";
     }
     // A base class always needs virtual constructor in order to avoid memory leaks.
     virtual ~BaseA() = default;

     void printName(){
        std::cout << " A - [printName] => Name = " << _name << "\n";
     }  
     void printNameA(){
        std::cout << " [printNameA] => NameA = " << _name << "\n";
     }
     void callOverridenVFun(){
        std::cout << " [BaseB::callOverridenVFun] BaseB::overridenVfun() returned"
                  << '\n' << " +=> RETURN = " << this->overridenVfun() << "\n";
     }
     virtual std::string overridenVfun() = 0;   

     virtual std::string nonOverridenVfun(){
        return "BaseA::nonOverridenVfun";
     }

     virtual std::string specificAVFun(){
       return "specificAVFun not overriden.";
     }

     static void staticMethod(){
       std::cout << " [BaseA] I am a static method from BaseA" << "\n";
     }  
};
  • Base class BaseB
class BaseB{
    // Only derived classes can access 
protected: 
    std::string key = "KEY-BASEB";

   // Derived classes and any external code can access  
public:    
    std::string _name;

    BaseB(std::string name): _name(name) {
        std::cerr << " [LOG] BaseB instantiated. OK." << "\n";
    }

    virtual ~BaseB() = default;

    void printName(){
        std::cout << " B - [printName] => Name = " << _name << "\n";
    }
    void printNameB(){
        std::cout << " [PrintName] => NameB = " << _name << "\n";
    }

    void callOverridenVFun(){
        std::cout << " [BaseB::callOverridenVFun] BaseB::overridenVfun() returned"
                  << '\n' << " +=> RETURN = " << this->overridenVfun() << "\n";
    }
    virtual std::string overridenVfun(){
        return " BaseB::overridenVfun not overriden";
    }   
    virtual std::string nonOverridenVfun(){
        return "BaseB::nonOverridenVfun";
    }
    virtual std::string specificBVFun(){
        return "specificBVFun not overriden.";
    }
    static void staticMethod(){
        std::cout << " [BaseB] I am a static method from BaseB" << "\n";
    }
};

  • Class DerivedD - inherits both class BaseA and BaseB.
class Derived: public BaseA, public BaseB{  
public:
    // Base class constructos must always be initialized through
    // initialization list like this.
    Derived()
      : BaseA("nameA"), BaseB("nameB")
    {       
    }
    Derived(std::string nameA, std::string nameB)
      : BaseA(nameA), BaseB(nameB)
    {       
    }
    ~Derived(){
        std::cout << " [Derived] Destructor called. " << "\n";
    }
    // Access ambiguous protected field from base class 
    void showKeyBaseA(){
        std::cout << " [showKeyBaseA] Key-BaseA = " << BaseA::key << "\n";
        std::cout << " [showKeyBaseA] Key-BaseA = " << this->BaseA::key << "\n";
    }
    void showKeyBaseB(){
        std::cout << " [showKeyBaseB] Key-BaseB = " << BaseB::key << "\n";
        std::cout << " [showKeyBaseB] Key-BaseB = " << this->BaseB::key << "\n";
    }

    void printNameX(){
        BaseA::printName();
        // Or 'this' pointer can be used too.
        this->BaseB::printName();
    }

  /* The derived class overrides both virtual member functions, BaseA::overridenVfun()
    * and BaseB::overridenVfun(). As a result, both base classes will share the derived class 
    * implementation.
    */
    #if true
    std::string overridenVfun() override {
         return "Both functions overriden, BaseA::overridenVfun() and BaseB::overridenVfun(). OK.";
    }
    #endif 

    // std::string specificAVFun() override {
    std::string specificAVFun() override{
        return "Overriden: specificAVFun";
    }
    std::string specificBVFun() override {
        return "Overriden: specificBVFun";
    }

    static void staticMethod(){
       std::cout << " [Derived] I am a static method from Derived" << "\n";
    }
};

Loading the code in ROOT REPL:

>> .L inheritance-multiple-ambiguity.cpp 

Creating an instance of derived class named Derived.

>> Derived d;
 [LOG] BaseA instantiated. OK.
 [LOG] BaseB instantiated. OK.
>> 

Access ambiguous publi field

  • Problem:
// FAILURE - Compilation error: 
>> d._name
ROOT_prompt_2:1:3: error: member '_name' found in multiple base classes of different types
d._name
  ^
inheritance-multiple-ambiguity.cpp:12:14: note: member found by ambiguous name lookup
        std::string _name;
                    ^
inheritance-multiple-ambiguity.cpp:53:14: note: member found by ambiguous name lookup
        std::string _name;
  • Solution: Use fully qualified field name. d.BaseA::_name or d.BaseB::_name. Or upcast pointer from derived to pointer to base class.
// SOLUTION: 
>> d.BaseA::_name
(std::string &) "nameA"

>> d.BaseB::_name
(std::string &) "nameB"

// Pointer to derived 
>> Derived* ptr_derived = &d;

>> ptr_derived->BaseA::_name
(std::string &) "nameA"

>> ptr_derived->BaseB::_name
(std::string &) "nameB"

// Access/refer by pointer - BaseA
>> BaseA* ptr_baseA = &d;

>> ptr_baseA->_name
(std::string &) "nameA"

// Access/refer by pointer - BaseB
>> BaseB* ptr_baseB = &d;

>> ptr_baseB->_name
(std::string &) "nameB"

Call non-ambiguous methods

>> d.printNameA()
 [printNameA] => NameA = nameA
// OR:
>> d.BaseA::printNameA()
 [printNameA] => NameA = nameA

>> d.printNameB()
 [PrintName] => NameB = nameB

// OR
>> d.BaseA::printNameA()
 [printNameA] => NameA = nameA

Call ambiguous method printName:

  • Problem:
// COMPILATION ERROR:
>> d.printName()
ROOT_prompt_5:1:3: error: member 'printName' found in multiple base classes of different types
d.printName()
  ^
inheritance-multiple-ambiguity.cpp:20:7: note: member found by ambiguous name lookup
        void printName(){
             ^
inheritance-multiple-ambiguity.cpp:61:7: note: member found by ambiguous name lookup
        void printName(){
             ^
  • Solution:
>> d.BaseA::printName()
 A - [printName] => Name = nameA

>> d.BaseB::printName()
 B - [printName] => Name = nameB
>> 

// Access/refer by pointer - BaseA
>> BaseA* ptr_baseA = &d;

>> ptr_baseA->printName()
 A - [printName] => Name = nameA

>> ptr_baseA->_name
(std::string &) "nameA"
>> 

// Access/refer by pointer - BaseA
>> BaseB* ptr_baseB = &d;

>> ptr_baseB->printName()
 B - [printName] => Name = nameB

>> ptr_baseB->_name
(std::string &) "nameB"
>> 

Invoke Overriden Virtual Function

  • If a base class overrides a virtual member function required by multiple base classes, all of them will share the same implementation supplied by the derived class. There is no way to override or supply a different implementations to each one of them in the derived class.
>> d.overridenVfun()
(std::string) "Both functions overriden, BaseA::overridenVfun() and BaseB::overridenVfun(). OK."
>> 

>> d.BaseB::overridenVfun()
(std::string) " BaseB::overridenVfun not overriden"
>> 

// Access/refer by pointer - BaseA
>> BaseA* ptr_baseA = &d;

>> ptr_baseA->overridenVfun()
(std::string) "Both functions overriden, BaseA::overridenVfun() and BaseB::overridenVfun(). OK."

// Access/refer by pointer - BaseB
>> BaseB* ptr_baseB = &d;

>> ptr_baseB->overridenVfun()
(std::string) "Both functions overriden, BaseA::overridenVfun() and BaseB::overridenVfun(). OK."
>> 

Invoke method callOverridenVFun

// Compilation error: 
>> d.callOverridenVFun()
ROOT_prompt_19:1:3: error: member 'callOverridenVFun' found in multiple base classes of different types
d.callOverridenVFun()

>> d.BaseA::callOverridenVFun()
 [BaseB::callOverridenVFun] BaseB::overridenVfun() returned
 +=> RETURN = Both functions overriden, BaseA::overridenVfun() and BaseB::overridenVfun(). OK.

>> d.BaseB::callOverridenVFun()
 [BaseB::callOverridenVFun] BaseB::overridenVfun() returned
 +=> RETURN = Both functions overriden, BaseA::overridenVfun() and BaseB::overridenVfun(). OK.
>> 

// Access/refer by pointer - BaseA
>> BaseA* ptr_baseA = &d;

// No ambiguity happens 
>> ptr_baseA->callOverridenVFun()
 [BaseB::callOverridenVFun] BaseB::overridenVfun() returned
 +=> RETURN = Both functions overriden, BaseA::overridenVfun() and BaseB::overridenVfun(). OK.
>> 

// Access/refer by pointer - BaseB
>> BaseB* ptr_baseB = &d;

>> ptr_baseB->callOverridenVFun()
 [BaseB::callOverridenVFun] BaseB::overridenVfun() returned
 +=> RETURN = Both functions overriden, BaseA::overridenVfun() and BaseB::overridenVfun(). OK.

Call functions showKeyBaseA and showKeyBaseB:

>> d.showKeyBaseA()
 [showKeyBaseA] Key-BaseA = KEY-BASEA
 [showKeyBaseA] Key-BaseA = KEY-BASEA
>> 
>> d.showKeyBaseB()
 [showKeyBaseB] Key-BaseB = KEY-BASEB
 [showKeyBaseB] Key-BaseB = KEY-BASEB
>> 

Invoke a non-overriden ambiguous virtual function.

>> d.nonOverridenVfun()
ROOT_prompt_40:1:3: error: member 'nonOverridenVfun' found in multiple base classes of different types
d.nonOverridenVfun()
  ^

>> d.BaseA::nonOverridenVfun()
(std::string) "BaseA::nonOverridenVfun"
>> 

>> d.BaseB::nonOverridenVfun()
(std::string) "BaseB::nonOverridenVfun"
>> 

// Pointer to derived 
>> Derived* ptr_derived = &d;

>> ptr_derived->BaseA::nonOverridenVfun()
(std::string) "BaseA::nonOverridenVfun"
>> 
>> ptr_derived->BaseB::nonOverridenVfun()
(std::string) "BaseB::nonOverridenVfun"
>> 

Call static methods:

>> BaseA::staticMethod()
 [BaseA] I am a static method from BaseA

>> BaseB::staticMethod()
 [BaseB] I am a static method from BaseB

>> Derived::staticMethod()
 [Derived] I am a static method from Derived
>>

>> d.staticMethod()
 [Derived] I am a static method from Derived
>> 

>> d.BaseA::staticMethod()
 [BaseA] I am a static method from BaseA

>> d.BaseB::staticMethod()
 [BaseB] I am a static method from BaseB
>> 

// Pointer to derived 
>> Derived* ptr_derived = &d;

>> ptr_derived->staticMethod()
 [Derived] I am a static method from Derived
>> 

// Access/refer by pointer - BaseA
>> BaseA* ptr_baseA = &d;

// Access/refer by pointer - BaseB
>> BaseA* ptr_baseB = &d;

>> ptr_baseA->staticMethod()
 [BaseA] I am a static method from BaseA

>> ptr_baseB->staticMethod()
 [BaseB] I am a static method from BaseB
>> 

1.22.2 TODO OOP Diamond problem

1.23 Type Casting / Type Conversion

1.23.1 Overview

In addition to the old C-style castiung, C++ supports new casting operators which are more type-safe and make the program's intent explicit, thus enhancing the readability and maintanability.

C-style Casting

  • (NEW-TYPE) <EXPRESSION>
  • Note: should be avoided.
  • Example:
    • char x = (char) 65;

C++ Sytle-Casting

  • static_cast<TYPE>(EXPR)
    • Use-case: implicity type conversion of related types, conversion of base class to derived class, void* pointer to othe pointer (except function pointer.). static_cast is the most used C++ casting operator.
    • When happens: this casting is performed at compile-time.
    • Can:
      • Convert between related types such as char to int, int to double,
      • Convert from and to void* pointer to other pointers: void* to double*, double* to void* and so on.
      • Convert base class to derived class (downcasting).
    • Cannot:
      • Convert between unrelated types such as unsigned long to double*.
      • Convert between pointers of unrelated types, for instance int* to double* or unsigned long to double*.
  • reinterpret_cast<TYPE>(EXPR);
    • Use-cases: This is a dangerous low level casting used for converting unrelated types. WARNING: failed conversion can result in segmentation fault, core dump and subtle crashing use with care.
      • Converting pointers of unrelated types such as double* to int*
      • Convert void* to function pointer.
        • Loading C-functions from shared libraries (.DLL files on Windows or .SO on U-nix, Linux, BSD, OSX …)
        • Implement type erasure of function-pointers for reflection libraries.
      • Access hardware registers of Embedded Systems (_memory mapped_ IO). Many embedded systems such as microcontrollers have memory mapped-IO which are hardware devices like Analog to Digital Convertes, digital Input/Output ports bound to specific memory addresses assigned by the manufacturer. By writing to those memory locations, it is possible to configure, control and read data from hardware devices. reinterpret_cast can be used for converting the numerical address given in usigned hexadecimal to some pointer of type int*, char*, double* and so on.
  • dynamic_cast<TYPE>(EXPR);
    • Use cases: Casting for polymorphic types, class hierachies for identifying at runtime instances of derived classes. This is part of the C++ RTTI - Runtime Type Identification.
    • When happens: this casting is performed at run-time.
  • const_cast<TYPE>(EXPR);
    • Use-case: Create non-const reference to const variable, so it can bypass the const constraint.

1.23.2 Example: Old C-Style Casting:

>> int k = 10;
>> int z = 7;

>> (float) k
(float) 10.0000f

>> float(k) / z
(float) 1.42857f
>> 

>> int m = 65
(int) 65

>> (char) m
(char) 'A'
>> 

>> (char) (m+1)
(char) 'B'
>> (char) (m+2)
(char) 'C'
>> (char) (m+3)
(char) 'D'
>> (char) (m+5)
(char) 'F'
>> 

1.23.3 Example: Static Casting

  • Implicit conversion:
>> int k = 10;
>> int z = 7;

>> k / z
(int) 1

>> static_cast<double>(k) / z
(double) 1.4285714

>> static_cast<float>(k) / z
(float) 1.42857f
>> 

>> int m = 65;
>> char ch;
>> ch = m
(char) 'A'
>> 
// Implicit conversion 
>> ch = static_cast<char>(m)
(char) 'A'
>> ch = static_cast<char>(m + 1)
(char) 'B'
>> ch = static_cast<char>(m + 2)
(char) 'C'
>> ch = static_cast<char>(m + 3)
(char) 'D'
>> 
  • Pointer conversion:

Conversion between pointer of unrelated types is not possible with static casting.

>> double x = 10.2;
>> int k = 10;
>> x

>> static_cast<int*>(&x)
ROOT_prompt_5:1:1: error: static_cast from 'double *' to 'int *' is not allowed
static_cast<int*>(&x)
^~~~~~~~~~~~~~~~~~~~~
>> 


Conversion to and from void pointer to other pointer types (except function pointer) is possible:

>> double x = 10.2;
>> int k = 10;

>> static_cast<void*>(&x)
(void *) 0x7f639f3ca010
>> 
>> void* ptrErased = static_cast<void*>(&x)
(void *) 0x7f639f3ca010
>> 
>> static_cast<double*>(ptrErased)
(double *) 0x7f639f3ca010
>> *static_cast<double*>(ptrErased)
(double) 10.200000
>> 

>> ptrErased = &k
(void *) 0x7f639f3ca018
>> static_cast<double*>(ptrErased)
(double *) 0x7f639f3ca018

// However, it may fail without any notice. 
>> *static_cast<double*>(ptrErased)
(double) 4.9406565e-323
>> 

>> static_cast<int*>(ptrErased)
(int *) 0x7f639f3ca018
>> *static_cast<int*>(ptrErased)
(int) 10
>> 

Conversion from number (numeric value of address) to to pointer is not possible with static casting.

>> &x
(double *) 0x7f639f3ca010
>> 
>> double* ptrX = static_cast<double*>(0x7f639f3ca010)
ROOT_prompt_18:1:16: error: cannot cast from type 'long' to pointer type 'double *'
double* ptrX = static_cast<double*>(0x7f639f3ca010)
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> double* ptrX = static_cast<double*>(0x7f639f3ca010UL)
ROOT_prompt_19:1:16: error: cannot cast from type 'unsigned long' to pointer type 'double *'
double* ptrX = static_cast<double*>(0x7f639f3ca010UL)
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> 
  • Conversion between polymorphic types / Classes from a given hierarchy.
using cstring = const char*;

class Base {
public:
        Base() = default;
        virtual ~Base() = default;
        virtual cstring getID() const {
                return "Base";
        }
};

class DerivedA: public Base{
public:
         cstring getID() const override {
                return "DerivedA";
        }
        void showA(){
                std::cout << "Exclusive method of class A." << "\n";
        }
};

class DerivedB: public Base{
public:
        cstring getID() const override {
                return "DerivedB";
        }
        double method(double x){
                return 3 * x;
        }
        void showB(){
                std::cout << "Exclusive method of class B." << "\n";
        }
};

Base     b;
DerivedA da;
DerivedB db;

Base* ptr = nullptr;

>> ptr = &b
(Base *) 0x7fd81941b010
>> ptr->getID()
(cstring) "Base"
>> 

>> ptr = &da;
>> ptr->getID()
(cstring) "DerivedA"

>> ptr = &db;
>> ptr->getID()
(cstring) "DerivedB"
>> 

// Downcast/Convert at compile-time pointer from the base class
// to derived class
//-------------------------------------------------
// Try access exclusive method:
>> ptr = &da;
>> ptr->showA()
ROOT_prompt_42:1:6: error: no member named 'showA' in 'Base'
ptr->showA()
~~~  ^

>> static_cast<DerivedA*>(ptr)->getID()
(cstring) "DerivedA"

>> static_cast<DerivedA*>(ptr)->showA()
Exclusive method of class A.
>> 

// Note: Can result in undefined behavior!
>> static_cast<DerivedB*>(ptr)->showB()
Exclusive method of class B.

>> 
>> ptr = &db;
>> static_cast<DerivedB*>(ptr)->getID()
(cstring) "DerivedB"
>> static_cast<DerivedB*>(ptr)->showB()
Exclusive method of class B.
>> static_cast<DerivedB*>(ptr)->method(3.0)
(double) 9.0000000
>> 
  • Conversio from/to void to function pointer is not possible.
>> double someFunction(int x, int y){ return 3.0 * x + y; }

>> someFunction(3, 5)
(double) 14.000000

>> void* ptrErasure = nullptr;

>> ptrErasure = static_cast<void*>(someFunction)
ROOT_prompt_26:1:14: error: static_cast from 'double (*)(int, int)' to 'void *' is not allowed
ptrErasure = static_cast<void*>(someFunction)
             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// However, it is possible with the old C-style casting. 
>> ptrErasure = (void*) &someFunction
(void *) 0x7f639f3730a0

// Or: 
>> ptrErasure = (void*) someFunction
(void *) 0x7f639f3730a0
>> 

// But, it works with C-style casting.
>> auto fnPointer2 = (double (*)(int, int)) ptrErasure
(double (*)(int, int)) Function @0x7f639f3730a0
>> 
>> fnPointer2(3, 4)
(double) 13.000000
>> fnPointer2(5, 4)
(double) 19.000000

1.23.4 Example: Reinterpret Cast

  • Casting function/pointer from and to void*
    • Note: this is useful for type erasure and reflection and loading functions/symbols from shared libraries at runtime.
double someFunction(int x, int y){ return 3.0 * x + y; }
double mfun1(double x){ return x * x + 10 * x - 25; }

void* ptrErasure = nullptr;

// Testing function some function:
>> ptrErasure = reinterpret_cast<void*>(&someFunction)
(void *) 0x7fe3b065a080

>> auto fnPtr1 = reinterpret_cast<double (*) (int, int)>(ptrErasure)
(double (*)(int, int)) Function @0x7fe3b065a080

>> fnPtr1(3, 5)
(double) 14.000000
>> fnPtr1(5, 5)
(double) 20.000000
>> 
>> reinterpret_cast<double (*) (int, int)>(ptrErasure)(3, 5)
(double) 14.000000
>> reinterpret_cast<double (*) (int, int)>(ptrErasure)(4, 5)
(double) 17.000000
>> 

// Testing with mfun
// --> C-style casting  
>> ptrErasure = (void*) mfun1
(void *) 0x7f94856da090
>> ptrErasure = (void*) &mfun1
(void *) 0x7f94856da090
>> 

// Failed   
>> ptrErasure = static_cast<void*>(&mfun1)
ROOT_prompt_7:1:14: error: static_cast from 'double (*)(double)' to 'void *' is not allowed
ptrErasure = static_cast<void*>(&mfun1)

>> auto fnPtr2 = reinterpret_cast<double (*) (double)>(mfun1)
(double (*)(double)) Function @0x7f94856da090

>> fnPtr2(4)
(double) 31.000000
>> fnPtr2(5)
(double) 50.000000
>> 

// It is really dangerous as it can cast anything to anything!
// and the compiler cannot help.    
>> reinterpret_cast<double (*) (double)>(4)(5)
// *** Break *** segmentation violation

  • Cast address/pointer to int and print it in hexadecimal format.
>> 
>> double x;
>> &x
(double *) 0x7fd510b760a0
>> 
>> std::cout << std::hex << "0x" << reinterpret_cast<std::uintptr_t>(&x) << std::dec << "\n";
0x7fd510b760a0

void printAddress(void* ptr){
     std::cout << "Address of variable is = " 
               << std::hex << "0x" << reinterpret_cast<std::uintptr_t>(ptr)
               << std::dec 
               << "\n";
}


>> std::string s
(std::string &) ""
>> &s
(std::string *) 0x7fd510b760b0
>> 
>> double m
(double) 0.0000000
>> 
>> printAddress(&s)
Address of variable is = 0x7fd510b760b0
>> printAddress(&m)
Address of variable is = 0x7fd510b760d0
  • Casting memory locations to pointer.
    • Note: It doesn't have applicability in most cases, but it can be useful for accessing memory mapped IO.
>> double x = 10.0
(double) 10.000000
>> unsigned long n = 300
(unsigned long) 300
>> 

>> &x
(double *) 0x7f3fd32e9010
>> &n
(unsigned long *) 0x7f3fd32e9018
>> 

// =========== Variable x =================

// Failure! Doesn't compile
>> static_cast<double*>(0x7f3fd32e9010)
ROOT_prompt_4:1:1: error: cannot cast from type 'long' to pointer type 'double *'
static_cast<double*>(0x7f3fd32e9010)
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> 
>> reinterpret_cast<double*>(0x7f3fd32e9010)
(double *) 0x7f3fd32e9010
>> *reinterpret_cast<double*>(0x7f3fd32e9010)
(double) 10.000000
>> *reinterpret_cast<double*>(0x7f3fd32e9010) = 100.0
(double) 100.00000
>> x
(double) 100.00000
>> 

// =========== Variable n =================
>> reinterpret_cast<int*>(0x7f3fd32e9018)
(int *) 0x7f3fd32e9018
>> *reinterpret_cast<int*>(0x7f3fd32e9018)
(int) 300
>> *reinterpret_cast<int*>(0x7f3fd32e9018) = 100
(int) 100
>> int& z = *reinterpret_cast<int*>(0x7f3fd32e9018)
(int) 100
>> z  = 500
(int) 500
>> n
(unsigned long) 500
>> n = 600
(unsigned long) 600
>> z
(int) 600
>> 
  • Load Symbols from a DLL / Shared Object
#include <dlfcn.h>

// GNU Scientific Library - Linear Algebra CBLAS 
auto dllPath = "/usr/lib64/libgslcblas.so";

>> void* libHandle = dlopen(dllPath, RTLD_LAZY)
(void *) 0x1be2de0
>> 

>> void* sym = dlsym(libHandle, "cblas_daxpy")
(void *) 0x7f3fb97d2010

>> sym == nullptr
(bool) false
>> 

>> auto cblas_daxpy = reinterpret_cast<void (*) (int, double, const double*, int, double*, int)>(sym)
(void (*)(int, double, const double *, int, double *, int)) Function @0x7f3fb97d2010
>> 

// Or 
using cblas_daxpy_type = void (int, double, const double*, int, double*, int);

>> auto xs = std::vector<double>{ 3.0, 5.0, 6.0, 10.0, 8.0};
>> auto ys = std::vector<double>{ 2.0, 2.0, 2.0,  2.0, 2.0};
>> 

>> cblas_daxpy(xs.size(), 4.0, &xs[0], 1, &ys[0], 1)
>> xs
(std::vector<double, std::allocator<double> > &) 
{ 3.0000000, 5.0000000, 6.0000000, 10.000000, 8.0000000 }

>> ys
(std::vector<double, std::allocator<double> > &) 
{ 14.000000, 22.000000, 26.000000, 42.000000, 34.000000 }
>> 

// Close the library handle (Better use SMART POINTERS!!!!)
>> dlclose(libHandle)
(int) 0
>> 

1.23.5 Example: Const cast

Const cast is used for removing the const qualifier/modifier from const reference or const pointer in order to modify the referenced memory location.

>> double x = 10.0;
>> const double& xref = x;
>> xref
(const double) 10.000000
>> 
>> xref = 20.0
ROOT_prompt_3:1:6: error: cannot assign to variable 'xref' with const-qualified type 'const double &'
xref = 20.0
~~~~ ^
ROOT_prompt_1:1:15: note: variable 'xref' declared const here
const double& xref = x;
~~~~~~~~~~~~~~^~~~~~~~
>> 
// Remove the const qualifier of xref 
>> const_cast<double&>(xref) = 25.0
(double) 25.000000
>> x
(double) 25.000000
>> xref
(const double) 25.000000
>> 

// Creating a non-const reference from a const reference.
>> double& usualRef = const_cast<double&>(xref) 
(double) 25.000000
>> usualRef = 16.0
(double) 16.000000
>> x
(double) 16.000000
>> xref
(const double) 16.000000
>> 

1.24 OOP RTTI - Runtime Type Identification

1.24.1 Overview

RTTI - Runtime Type Identification is the ability to provide information about types at rutime rather than at compile time. It is a limited form of reflection. In C++, the RTTI functionality is provided by the operators typeid getting type information and dynamic_cast used for safely casting polymorphic types.

RTTI in C++:

  • Operator dynamic_cast
  • Operator typeid
  • Class std::type_info
  • Exception: std::bad_typeid (derived class of std::exception)

Further Reading

1.24.2 Operator typeid

The operator typeid retrives information about a given type by returning a reference to an object of type typeinfo.

Header:

  • <typeinfo>

Operator typeid:

const typeinfo& typeid(ARGUMENT);

Use cases:

  • Compare types.
  • Recover wrapped type from type erasure. This approach is used by Boost.Any and std::any from C++17.
  • Debugging

Class std::type_info:

class type_info{
public:
   virtual ~type_info();
   bool operator==(cons type_info& rhs) const;
   bool operator!=(cons type_info& rhs) const;
   bool before( const type_info& rhs ) const;
   const char* name() const;
   // Since: C++11
   size_t hash_code() const;
};

Note: the type name returned by method .name() of type_info returns the decorated name of type or the mangled name of type. The value returned is compiler-dependent which means that a code should not rely on the returned name.

Example:

Get type name:

>> typeid(int).name()
(const char *) "i"

>> typeid(double).name()
(const char *) "d"

>> typeid(float).name()
(const char *) "f"

>> typeid(long).name()
(const char *) "l"

>> typeid(int*).name()
(const char *) "Pi"

>> typeid(double*).name()
(const char *) "Pd"

>> typeid(float*).name()
(const char *) "Pf"

>> typeid(long*).name()
(const char *) "Pl"

>> typeid(void).name()
(const char *) "v"

>> typeid(void*).name()
(const char *) "Pv"

>> typeid(void**).name()
(const char *) "PPv"
>> 

>> typeid(std::vector<double>).name()
(const char *) "St6vectorIdSaIdEE"

>> typeid(std::deque<double>).name()
(const char *) "St5dequeIdSaIdEE"

Explore type_info object:

>> const std::type_info& ti = typeid(3.434);
>> ti.name()
(const char *) "d"

>> ti.hash_code()
(unsigned long) 14494284460613645429

// Check whether type is int 
>> ti == typeid(int)
(bool) false

// Check whether type is double 
>> ti == typeid(double)
(bool) true

// Return the managled name of type 
>> typeid(std::string).name()
(const char *) "NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE"

>> typeid(std::string*).name()
(const char *) "PNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE"


>> typeid(std::string).hash_code()
(unsigned long) 5774750460303204477

typeid and polymorphic types:

class Base{ virtual void dummy(){} };
class Da: public Base{};
class Db: public Base{};

>> typeid(Base).name()
(const char *) "4Base"

>> typeid(Da).name()
(const char *) "2Da"

>> typeid(Db).name()
(const char *) "2Db"

>> typeid(Base*).name()
(const char *) "P4Base"

>> typeid(Da*).name()
(const char *) "P2Da"

1.24.3 Operator dynamic_cast

The operator dynamic_cast is used for safely downcasting a base class to a particular derived class and is also for checking whether a pointer to the base class refers to an instance of a particular derived class. In general, dynamic casting should be avoided as it violates OOP good practices, for instance, the client code in most cases should have no knowledge about which particular derived class it is dealing with and it also should be able to work with new derived classes without any modification. The usage of dynamic cast and if-else in a client code for performing operation specific for each derived class can be avoided by adding new a virtual member function to the base class or by using the visitor design pattern or double dispatching.

The operator static_cast can also be used for downcasting, however if the operation is not possible, the result is undefined, thus using static_cast is unsafe. It is only safe using static_cast, if there is no doubt that the conversion is possible.

Dynamic cast use case(s):

  • Extend the functionality of a class hierarchy without modifying the base class or adding new virtual member functions. However, it will require modification of client code if a new derived class is created.

Notes and remarks:

  • The operator dynamic_cast only works with polymorphic types, in other words, any class with at least one virtual member function.
  • dynamic_cast operator only works with pointers or references to classes with at least one virtual method.
  • static_cast downcasting is unsafe. There is undefined behavior if the conversion is not possible.
  • static_cast is a compile-time construct.
  • dynamic_cast is a run-time construct.

Casting Pointers

If the conversion fails, the pointer to the derived class is set to null and no exception is thrown.

BaseClass* pointerToBase = &objectOfDerivedA;

// Downcast pointer from base to derived class
DerivedClass* pointertoDerived = dynamic_cast<DerivedClass*>(pointerToBase);
// Or
auto pointertoDerived = dynamic_cast<DerivedClass*>(pointerToBase);

if(pointertoDerived){
  std::cout << "It is the derived class" << "\n";
  pointertoDerived->exclusiveMethod1();
  ... ... .. 
} else { 
  std::cout << "Conversion failed." << "\n";
}

Casting References

In this case, if the casting fails, it throws a std::bad_cast exception.

BaseClass& refBaseClass = objectOfDerivedA;   
try{
   // Downcast reference 
   DerivedClass& refDerived = dynamic_cast<DerivedClass&>(refBaseClass);
   std::cout << "Found derived class ..." << "\n";
   refDerived.exclusiveMethod1(); 
   refDerived.exclusiveMethod2(); 
} catch(const std::bad_cast& ex){
   std::cerr << "Error: casting not possible." << "\n";
   std::cerr << " [ERROR] " << ex.what() << "\n";
}

Example:

class Base{
public:
    Base() = default;
    // Destructor of base class must always be virtual 
    virtual ~Base() = default;   
      virtual auto getType() const -> std::string {
      return "Base";
   }    
   void showType(){
      std::cout << "Class type = " << this->getType() << "\n";
   }
};

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

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

Creating testing objects:

Base base;
DerivedA da;
DerivedB db;
>> Base *ptr = nullptr;

// Point to stack-object base 
>> ptr = &base
(Base *) 0x7f6d9a022010

// Check whether pointed object is of type base 
>> Base* ptrBase = dynamic_cast<Base*>(ptr);

>> if(ptrBase) { std::cout << "Object of type Base" << "\n";}
Object of type Base
>> 

>> if(dynamic_cast<Base*>(ptr)) { std::cout << "Object of type Base" << "\n";}
Object of type Base
>> 

Point to the object da (DerivedA)

>> ptr = &da;

// Cast pointer 
>> DerivedA* ptrDA = dynamic_cast<DerivedA*>(ptr)
(DerivedA *) 0x7f6d9a022018

>> if(ptrDA != nullptr){ std::puts("Object of type DerivedA"); }
Object of type DerivedA

>> if(ptrDA){ std::puts("Object of type DerivedA"); }
Object of type DerivedA
>> 

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

// Check whether pointer refers to an object of type DerivedA
>> if(dynamic_cast<DerivedA*>(ptr)) { std::cout << "Object of type A" << "\n";}
Object of type A

Check whether pointer refers to an object of type DerivedB

  • If the casting is not possible, the dynamic_cast operator returns a null pointer.
>> ptr = &da;

>> DerivedB* ptrDB = dynamic_cast<DerivedB*>(ptr)
(DerivedB *) nullptr
>> 

>> if(ptrDB != nullptr) { std::puts("=> Type DerivedB"); } else { std::puts("Casting failed"); }
Casting failed
>> 

>> if(ptrDB){ std::puts("=> Type DerivedB"); } else { std::puts("Casting failed"); }
Casting failed
>> 

>> if(dynamic_cast<DerivedB*>(ptr)) { std::puts("=> Type DerivedB"); } else { std::puts("Casting failed"); }
Casting failed
>> 

>> if(dynamic_cast<DerivedA*>(ptr)) { std::puts("=> Type DerivedA"); } else { std::puts("Casting failed"); }
=> Type DerivedA
>> 

Function which applies operations specific to the derived class using pointers:

void processType(Base* ptr){
     auto ptrA = dynamic_cast<DerivedA*>(ptr);
     if(ptrA){
         std::puts("Found object of type = DerivedA");
         std::cout << ".getType() = " << ptrA->getType() << "\n";
         return;
     }
     auto ptrB = dynamic_cast<DerivedB*>(ptr);
     if(ptrB){
        std::puts(" Found object of type = DerivedB");
        std::cout << ".getType() = " << ptrB->getType() << "\n";
        return;
     }
     if(ptr){
        std::puts("Found object of type Base");
        std::cout << ".getType() = " << ptr->getType() << "\n";
        return;
     }
     std::puts("Null pointer or cannot determine instance type.");
}


>> processType(&da)
Found object of type = DerivedA
.getType() = DerivedA

>> processType(&db)
 Found object of type = DerivedB
.getType() = DerivedB

>> processType(&base)
Found object of type Base
.getType() = Base

>> processType(nullptr)
Null pointer or cannot determine instance type.

Dynamic casting references:

  • Note: References to polymorphic objects can be casted with dynamic cast too. In this case if the operation fails, it throws an std::bad_cast exception.
>> Base& refb1 = base;

// Throws exception, casting failed 
>> DerivedA& refa1 = dynamic_cast<DerivedA&>(refb1)
Error in <TRint::HandleTermInput()>: std::bad_cast caught: std::bad_cast
>> 

>> Base& refb2 = da;
>> 
>> DerivedA& refa2 = dynamic_cast<DerivedA&>(refb2)
(DerivedA &) @0x7f9d32750018
>> 
>> refa2.getType()
(std::string) "DerivedA"
>> 

1.25 Friend Functions

A friend function is a function that can access the private and protected member variables or member functions (methods) of some class. There are lots of controversies about friend function over breaking of encapsulation, however there are legitimate cases for their usage such as serialization.

Features:

  • A friend function to some class has access to private and protected member variables and functions of that class.
  • F.F. can be friend to multiple classes via function overloading.
  • F.F. are not member functions or part of any class.

Example:

Class: ClassA

class ClassA{
private:
     std::string m_id;
     double m_x, m_y;
     void describe() const {
        std::cout << "This is the class: ClassA" << std::endl;
     }  
public:
    ClassA(const std::string& id, double x, double y):
       m_id(id), m_x(x), m_y(y)
    {
    }
    double GetX() const { return m_x; }
    double GetY() const { return m_y; } 
    friend void showClassInfo(ClassA const& obj);
}

Class: ClassB.

class ClassB{
private:
     std::string  m_id;
     int m_a, m_b, m_c;
public:
    ClassB(std::string const& id, int a, int b, int c):
            m_id(id), m_a(a), m_b(b), m_c(c)
    {       
    }
    friend void showClassInfo(ClassB const& obj);
};

Friend Implementation:

void showClassInfo(ClassA const& obj){
     std::cout << "ClassA Information." << "\n";
     std::cout << "  id = "    << obj.m_id
               << " ; x = " << obj.m_x
               << " ; y =  " << obj.m_y
               << "\n";
     obj.describe();
}

void showClassInfo(ClassB const& obj){
    std::cout << "ClassB Information." << "\n";
    std::cout << " id = " << obj.m_id
              << " ; a = " << obj.m_a
              << " ; b = " << obj.m_b
              << " ; c = " << obj.m_c
              << "\n";
}

ROOT REPL Testing:

>> ClassA clsA{"objectA", 203.67, 9.345};
>> ClassB clsB{"objB", 20, 824, 561};

>> clsA.m_x
ROOT_prompt_8:1:6: error: 'm_x' is a private member of 'ClassA'
clsA.m_x
     ^
input_line_17:5:9: note: declared private here
        double m_x, m_y;
               ^

>> showClassInfo(clsA)
ClassA Information.
  id = objectA ; x = 203.67 ; y =  9.345
This is the class: ClassA


>> showClassInfo(clsB)
ClassB Information.
 id = objB ; a = 20 ; b = 824 ; c = 561

Further Reading:

1.26 Namespaces

1.26.1 Summary

C++ Namespace Python Equivalent Description
Operation    
using namespace std; from std import * Import everything from a namespace.
using std::cout; from std import cout Import an object, function from a namespace.
using std::cout, std::cin, std::endl; from std import cout, cin … Import multiple items from a namespace (C++17 only.)
namespace mk = mathkit::ellipticfun; import mathkit.ellipticfun as mk Create an alias to a namespace.
mathkit::function::sind(90); import mathkit; mathkit.function.sind(90); Call a function from namespace.
     

1.26.2 Examples

Open namespace:

  • using namespace <namespace>
// Not recommended - It defeats the purpose of namespace which is prevent 
// nameclashes and improve discoverability. 
using namespace std;
std::cout << "Hello world" << "\n";

// Import everything from boost linear algebra library.
using namespace boost::numeric::ublas;

Open namespace inside function:

void printNumbers() {
        // Import everything from std namespace 
        using namespace std;
        for(int i = 0; i < 10; i++)
                cout << "i = " << i << "\n";
}

// C++11 auto syntax for function declaration
auto printNumbers() -> void {
        using namespace std;
        for(int i = 0; i < 10; i++)
                cout << "i = " << i << "\n";
}

// C++14 return type deduction with optional type deduction.
auto printNumbers() {
        // Import everything from std namespace 
        using namespace std;
        for(int i = 0; i < 10; i++)
                cout << "i = " << i << "\n";
}

Using element from namespace without import namespace.

std::cout << "hello world" << std::endl;
auto matrixI3 =  boost::numeric::ublas::identity_matrix<double>(3));

Import specific elements from namespace

// Option 1 
//----------------------------------------//
using std::cout; 
using std::cerr; 
using std::endl; 
using boost::numeric::ublas::identity_matrix;
using boost::numeric::ublas::norm_1;

// Option 2 => Multiple imports at same line. 
//----------------------------------------//
using std::cout; using std::cerr;  using std::endl; 
using boost::numeric::ublas::identity_matrix; using boost::numeric::ublas::norm_1;

// Option 3 => C++17 only 
//----------------------------------------//
using std::cout, std::cerr, std::endl; 
using boost::numeric::ublas::identity_matrix, boost::numeric::ublas::norm_1;

Namespace synonym/alias:

// Create namespace alias ub 
namespace ub = boost::numeric::ublas;

ub::matrix<double> matrix1(3, 3, 2.5);
// auto type inference + uniform initalization 
auto matrix1 = ub::matrix<double> {3, 3, 2.5};

Creating a namespace:

#include <iostream>
#include <ostream>
#include <functional>
#include <cmath>

namespace MyFunctions{
    // Import everything from namespace std 
    using namespace::std;

    // Type alias or synonym 
    using cstring = const char*;

    // Or: using MathFunc = std::function<double (double)>;
    using MathFunc = std::function<auto (double) -> double>;

    cstring description = "Utility functions";

    double add(double x, double y){
       return x + y;
    }

    auto saySomething() -> void {
        cout << "A computer was a skilled mathematician who computed firing tables." << endl;
    }
    struct Coord{
        // Latitude in degrees/decimal 
        double lat;
        // Longitude in degrees/decimal 
        double lon;
    };

    namespace math {
        auto showTable(MathFunc fun, std::ostream& os = std::cout) -> void{
            for(double x = 0; x < 10.0; x += 1.0){
               os << setw(10) << x << setw(10) << fun(x) << "\n";
            }
        }
        auto makeMultiplier(double x) -> MathFunc {
             return [x](double y){return x * x; };
        }       
    }

}; // End of namespace MyFunctions --/

Testing: (CERN's ROOT/Cling REPL.)

>> MyFunctions::add(102.3, -93.4)
(double) 8.9000000
>> MyFunctions::add(102.3, -193.4)
(double) -91.100000
>>
>> MyFunctions::saySomething()
A computer was a skilled mathematician who computed firing tables
>>

>> MyFunctions::math::showTable([](double x){ return x * 3.0 + 4.0;} )
         0         4
         1         7
         2        10
         3        13
  ... ... ... ... 

// import MyFunctions.math as m
>> namespace m = MyFunctions::math;
>> m::showTable([](double x){ return x * 3.0 + 4.0;} )
         0         4
         1         7
         2        10
         3        13
  ... ... ... ... 

// from MyFunctions.math import *
>> using namespace MyFunctions::math;

>> showTable([](double x){ return x * x;}, std::cerr )
         0         0
         1         1
         2         4
         3         9
         4        16
    ... ... ... ...  

>> showTable(exp, std::cerr )
ROOT_prompt_34:1:1: error: no matching function for call to 'showTable'
showTable(exp, std::cerr )
^~~~~~~~~

>> showTable(static_cast<double (*)(double)>(exp), std::cerr)
         0         1
         1   2.71828
         2   7.38906
         3   20.0855
         4   54.5982
         5   148.413
         6   403.429
         7   1096.63
         8   2980.96
         9   8103.08

Define a function at some namespace:

int General::GetNumberOfDays(int d1, int d2){
   return d2 - d1;
}

1.27 Move Semantics

1.27.1 r-value and l-values

Lvalues X Rvalues

The names LValues and RValues come from C, "L-value" name comes from the left-hand side from an assignment operation referring to a memory location from which it is possible to take the address. The "R-value" name come from the right-hand side from an assignment expression and is a temporary value from which it is not possible to take the address.

<Lvalue> = <Rvalue>; 
    |         |
    |         \+-------->> R-value: right-hand side of assignment, 
    \                      non-addressable. Not possible to take address, 
     \                     temporary object.
      \ 
       +----------------->> L-value: left-hand side of assignment, 
                            addressable memory location. 

So:

  • Lvalue => value with an addressable and identifiable memory location from which is possible to take the address with the ampersand operator (&), set or assign a new value.
  • Rvalue => literal, expression or temporary object from which it is not possible to take the address with ampersand operator(&) or set a new value. For short, it can be said that, everything that is not an L-value, is an R-value.
  • More at: value category (CppReference)

Examples:

// x => is a Lvalue, 10 is an Rvalue   
int x = 10;                             

// str1 is a Lvalue and "hello world" is a 
std::string str1 = "hello world";       

// str2 is a Lvalue and the right-hand side (str1 + " - rvalue") is a r-value 
std::string str2 = str1 + " - rvalue;";

class A{
public:
   A() { ... }
};

void Function(){ 
   A a; 
   .... ... 
   return a; 
}

A obj; 

//  obj is an L-value, while the object returned by 'Function()' is
//  a R-value (temporary object).
obj = Function(); 

1.27.2 r-value references and r-value references

LValue Reference

A L-value reference is an alias to a non-temporary object with a defined memory location, which is similar to a constant pointer.

// 'x' is a Lvalue => 10 is a R-value 
>> int x = 10;

// refx is a L-value reference bound to 'x', in other words, refers to 'x'. 
>> int& refx = x;
>> 
>> refx
(int) 10

// An L-value reference has the same address as the variable that the
// reference it is bound to.
>> if(&refx == &x) printf(" [INFO] Have the same address Ok.\n");
 [INFO] Have the same address Ok.

>> refx = 50
(int) 50
>> x
(int) 50

An L-value reference cannot bind to R-values or temporary objects:

>> 
>> int& refy = x + 10;
ROOT_prompt_6:1:6: error: non-const lvalue reference to type 'int' cannot bind to a temporary of type
      'int'
int& refy = x + 10;
     ^      ~~~~~~

Rvalue References

RValue references are references which binds to temporary objects. They were introduced in C++11 for enabling move semantics and move-member functions, namely, move constructor and move assignment operator which allow to reuse resources from temporary objects by performing shallow copy instead of deep copy, which would be performed by the copy-constructor and copy assignment operator. Therefore, as a shallow copy is much less expansive than a deep copy, move semantics can provide significant performance improvements eliminating wasteful copies of temporary objects.

  • Example 1: Rvalue binding.
>> int x = 10;

// R-value reference
>> int&& rv = x + 10;

>> rv
(int) 20

>> rv = 25
(int) 25

// Compile-time error! R-value references cannot bind to L-values.
>> int&& rv2 = x;
ROOT_prompt_22:1:7: error: rvalue reference to type 'int' cannot bind to lvalue of type 'int'
int&& rv2 = x;
      ^     ~
  • Example 2: Lvalue X Rvalue and function overload.

Attempt to use the L-value reference overload of the function printLines with a L-value and R-value.

#include <iostream>
#include <string> 
#include <fstream> 

// Overload of printLine taking an L-value reference as parameter 
void printLines(std::istream& is){
   std::cout << " [INFO] L-value reference overload" << std::endl;
   std::string line; 
   std::getline(is, line); 
   std::cout << " line1 =: " << line << std::endl;
   std::getline(is, line); 
   std::cout << " line2 =: " << line << std::endl;
}

>> std::ifstream fd1 {"/etc/protocols"};

// Use the function with an L-value reference 
>> printLines(fd1)
 [INFO] L-value reference overload
 line1 =: # /etc/protocols:
 line2 =: # $Id: protocols,v 1.12 2016/07/08 12:27 ovasik Exp $

Attempt to pass an R-value (temporary object in this case):

// 'std::ifstream{"/etc/protocols"}' is an R-value (temporary object)
// It is not possible to take address of this expression. 
// If the code was compiled, it would result in compile-time error. 
>> printLines(std::ifstream{"/etc/protocols"})

ROOT_prompt_10:1:1: error: no matching function for call to 'printLines'
printLines(std::ifstream{"/etc/protocols"})
^~~~~~~~~~
ROOT_prompt_0:1:6: note: candidate function not viable: no known conversion from 'std::ifstream' (aka
      'basic_ifstream<char>') to 'std::istream &' (aka 'basic_istream<char> &')
      for 1st argument
void printLines(std::istream& is){
    ^

Solution, create a new overload of the function 'printLines' that takes an r-value reference as parameter.

// Overload of printLine taking an R-value reference as parameter 
void printLines(std::istream&& is){
   std::cout << " [INFO] R-value reference overload" << std::endl;
   std::string line; 
   std::getline(is, line); 
   std::cout << " line1 =: " << line << std::endl;
   std::getline(is, line); 
   std::cout << " line2 =: " << line << std::endl;
}

>> printLines(std::ifstream{"/etc/protocols"})
 [INFO] R-value reference overload
 line1 =: # /etc/protocols:
 line2 =: # $Id: protocols,v 1.12 2016/07/08 12:27 ovasik Exp $

1.27.3 Move-semantics member functions

Move Constructor and move assignment operator

C++11 introduced new special member functions for move semantics, the move constructor and move assignment operator. Those special member functions were added for enhancing performance through elimination of temporary objects (R-values) deep copies, that were performed by the copy constructor or copy assignment operator.

Despite the name, the move constructor and move assignment operator don't move anything, they just transfer the ownership of resources from a temporary object (rvalue) to a new object, in the case of the move-ctor and to an existing object, in the case of the move-operator. After the transfer, the temporary object is left in a valid, but undetermined state without any resources.

Example:

If a class manages a heap-allocated array:

int* p_res = new int [10];

The move assignment operator copies the pointer (_shallow copy_) p_res from a temporary object, transferring it to an existing object on the left-hand side of the assignment, then the OldObject.p_res is set nullptr. Without the move assignment operator, the copy assignment operator would be called creating a wasteful deep copy due to a new array allocation and copy of all elements.

Benefits

  • The move semantics makes returning by value from functions or member functions more efficient than before C++11 as only shallow copy is performed, therefore the return value no longer need to be returned as function parameter reference.
  • All STL containers, such as std::vector, std::deque, std::unique_ptr, std::shared_ptr, std::map and so on, define move member functions which makes returning any instance of them by value more efficient and without any deep copy overhead.

Default member functions

Before C++11, if class did not define any user declared special member function, the compiler generated by default a default constructor, copy-constructor, copy-assignment operator and a destructor. Since C++11, in addition to those functions, the compiler also generates the move constructor and move assignment operator in the absence of any user declared member function.

Rule of three

Before C++11, if a class manages a resource such as heap-allocated object, file descriptor, socket handler, … and so. And the class implements a user-defined default destructor, copy constructor or copy-assignment operator, it should implement all of three.

Rule of five

Since C++11, if a class managing a resource implement a user-defined destructor, copy constructor, copy assignment operator, move constructor or move assignment operator, it should implement all of five.

  • Note 1: If the copy member functions are annotated as deleted, only the destructor and move member functions needs to be implemented.
  • Note 2: If the user do not define or annotate the copy-member functions as deleted, the compiler may provide default copy-member functions that are not aware of the resources. As a result, a shallow-copy will be performed instead of deep copy resulting in a bug when any of objects using the same resources disposes them while they are being used by other objects.
  • Note 3: Generally, classes that manages non-heap allocated resources such as sockets descriptors, handlers, database handlers should not be copiable, in other words, it should annotate copy member functions as deleted.

1.27.4 Signature of copy and move member functions

Signature of copy member functions

Copy constructor:

AClass::AClass(const AClass& rhs);
// Or 
AClass::AClass(const AClass & rhs);
// Or 
AClass::AClass(AClass const& rhs);

Copy assignment operator:

AClass& AClass::operator=(const AClass& rhs);
// Or 
AClass& AClass::operator=(AClass const& rhs);

Signature of move member functions

Move constructor:

AClass::AClass(AClass&& temporary);

Move assignment operator:

AClass& AClass::operator=(AClass&& temporary);

1.27.5 Example: Move Semantics Member Functions

Full source code:

Class: AClass.

  • The class AClass manages a heap-allocated object resource.
class AClass{
public:
    // Object unique ID for easier identification
    const int m_id;
    // Simulates a resource (heap-allocated object)
    int* m_p;

    static int make_id()
    {
        static int id = 0;
        return ++id;
    }

    // Default constructor
    AClass(): AClass(0) { }

    AClass(int n): m_id{AClass::make_id()}, m_p{new int {n}}
    {
        printf(" [TRACE] Object created => id = %d ; *m_p = %d\n", m_id, *m_p);
    }

    // Copy constructor
    AClass(AClass const& that): m_id{AClass::make_id()}
    {
        m_p = new int (*that.m_p);
        printf(" [TRACE] Copy ctor invoked => Copied src=%d dest=%d \n"
               , that.m_id, m_id);
    }

    // Copy assignment operator
    AClass& operator=(AClass const& that)
    {
        printf(" [TRACE] Copy asn. operator invoked. => src_id = %d - dest_id = %d \n"
               , that.m_id, m_id);
        *m_p = *that.m_p;
        return *this;
    }

#if 1
    // Move constructor: move resource from a temporary object (RValue) to a new object.
    AClass(AClass&& that): m_id{AClass::make_id()}
    {
        printf(" [TRACE] Move ctor invoked OK. => src_id = %d - dest_id = %d \n"
               , that.m_id, m_id);
        // Transfer ownership of resource managed by temporary object that
        // to this object.
        m_p = that.m_p;
        // Make the temporary object empty
        that.m_p = nullptr;
    }

    // Move assignment operator: move resource from a temporary object (RValue)
    // to an existing object (this).
    AClass& operator=(AClass&& that)
    {
        printf(" [TRACE] Move asn operator invoked. OK. src_id = %d - dest_id = %d \n"
               , that.m_id, m_id);
        int* p_temp = m_p;
        // Transfer ownership of resources from the temporary object(that) to
        // this object.
        m_p = that.m_p;
        // Diposes the resource of the existing object (this)
        that.m_p = p_temp;
        return *this;
    }
#endif

    // Destructor
    ~AClass()
    {
        printf(" [TRACE] Object destroyed => Dtor called => id = %d \n", m_id);
        delete m_p;
        m_p = nullptr;
    }

    int get() const { return *m_p; }
    void set(int n) { *m_p = n; }
};

Helper functions:

void is_lvalue_or_rvalue(AClass& arg)
{
    printf(" ===> Passed LVALUE \n");
}

void is_lvalue_or_rvalue(AClass&& arg)
{
    printf(" ===> Passed RVALUE \n");
}

Load the file in CERN's root repl:

>> .L move_member_funcs.cpp 

Create some testing object:

>> AClass obj1;
 [TRACE] Object created => id = 1 ; *m_p = 0

>> AClass obj2(8);
 [TRACE] Object created => id = 2 ; *m_p = 8

>> AClass obj3(10);
 [TRACE] Object created => id = 3 ; *m_p = 10

Check R-values and L-values:

// Pass an L-value 
>> is_lvalue_or_rvalue(obj1)
 ===> Passed LVALUE 
>> 

// Pass an R-value => temporary object
>> is_lvalue_or_rvalue(AClass(100))
 [TRACE] Object created => id = 4 ; *m_p = 100
 ===> Passed RVALUE 
 [TRACE] Object destroyed => Dtor called => id = 4 

>> is_lvalue_or_rvalue(make_object(4))
 [TRACE] Object created => id = 5 ; *m_p = 4
 [TRACE] FUN = make_object => Created object => id = 5 ; cls.get() = 8
 ===> Passed RVALUE 
 [TRACE] Object destroyed => Dtor called => id = 5 

Invoke copy constructor:

>> AClass objA(obj2);
 [TRACE] Copy ctor invoked => Copied src=2 dest=6 

>> objA.get()
(int) 8

>> obj2.get()
(int) 8

Return value-optimization RVO => neither the copy constructor or move constructor are called.

>> AClass objB(make_object(6));
 [TRACE] Object created => id = 7 ; *m_p = 4
 [TRACE] FUN = make_object => Created object => id = 7 ; cls.get() = 10

>> objB.m_id
(const int) 7

>> objB.get()
(int) 10

>> AClass objC = make_object(10);
 [TRACE] Object created => id = 8 ; *m_p = 4
 [TRACE] FUN = make_object => Created object => id = 8 ; cls.get() = 14
>> 
>> objC.m_id
(const int) 8
>> 
>> objC.get()
(int) 14
>> 

Force invocation of move constructor with std::move which casts an lvalue argument to an rvalue.

>> objB.m_p
(int *) 0x39c38d0

// ===> Before calling move ctor 

>> AClass objM { std::move(objB) };
 [TRACE] Move ctor invoked OK. => src_id = 7 - dest_id = 10 
>> 

// ===> After calling move ctor 

>> objB.m_p
(int *) nullptr
>> 

// The resource was transfered to object objM and objB no longer owns
// the resource.
>> objM.m_p
(int *) 0x39c38d0
>> 

>> *objM.m_p
(int) 10

The copy assignment operator is called when assigning an l-vavlue to an l-value or an addressable object to another.

>> AClass c1;
 [TRACE] Object created => id = 11 ; *m_p = 0

// Copy assignment operator called. 
//-----------------------------------
>> c1 = obj2;
 [TRACE] Copy asn. operator invoked. => src_id = 2 - dest_id = 11 

>> c1.m_p
(int *) 0x46c5de0

>> obj2.m_p
(int *) 0x2503fe0

>> c1.get()
(int) 8
>> 

The move assignment operator is called when assigning an l-value object to a temporary value or r-value. If a move constructor is not defined or was not created by the compiler, then the copy constructor is invoked resulting in an expensive deep copy.

>> AClass cx;
 [TRACE] Object created => id = 12 ; *m_p = 0

// Move assignment operator is called as cx is assigned to a temporary
// object (R-value)
// -------------------------------
>> cx = AClass(4);
 [TRACE] Object created => id = 13 ; *m_p = 4
 [TRACE] Move asn operator invoked. OK. src_id = 13 - dest_id = 12 
 [TRACE] Object destroyed => Dtor called => id = 13 

>> cx.get()
(int) 4
>> 

// Move assignment operator called as cx is assigned to a R-value 
>> cx = make_object(3);
 [TRACE] Object created => id = 14 ; *m_p = 4
 [TRACE] FUN = make_object => Created object => id = 14 ; cls.get() = 7
 [TRACE] Move asn operator invoked. OK. src_id = 14 - dest_id = 12 
 [TRACE] Object destroyed => Dtor called => id = 14 
>> 

>> cx.get()
(int) 7
>> 

Force invocation of the move-assignment operator by using std::move which casts the argument from l-value to r-value:

>> cx = obj1;
 [TRACE] Copy asn. operator invoked. => src_id = 1 - dest_id = 12 

>> cx = std::move(obj2);
 [TRACE] Move asn operator invoked. OK. src_id = 2 - dest_id = 12 

>> cx.get()
(int) 8

Simplifcation of move member functions

The move constructor and assignment operator could be rewritten in a more concise way by using the std::move and std::swap facilities from the standard library.

  • Move constructor:
// Move constructor: move resource from a temporary object (RValue) to a new object.
AClass(AClass&& that): m_id{AClass::make_id()}, m_p{nullptr}
{ 
    std::swap(m_p, that.m_p);
} 

Move assignment operator:

// Move assignment operator: move resource from a temporary object (RValue)
// to an existing object (this).
AClass& operator=(AClass&& that)
{
    std::swap(m_p, that.m_p);
    return *this;
}

Functions std::swap:

>> int xx = 10, yy = 20;

>> xx
(int) 10
>> yy
(int) 20

>> std::swap(xx, yy);
>> xx
(int) 20
>> yy
(int) 10
>> 

1.27.7 References and further reading

1.28 Smart Pointers

1.28.1 Problems of Raw Pointer

Raw pointers to dynamically allocated objects, (objects allocated with new and disposed with delete operator) are responsible for a broad range of problems such as memory leaks, double free and dangling pointers that affected many old codebases. Some of the problems and pontential bugs of raw pointers are:

  • memory leak
    • => The dynamically allocated object was not disposed. For every object allocated with AType* ptr = new AType(), there should be a matching delete operator. A memory leak happens when a call to this operator is missing. It can happen due to be hard to keep track of the onership of an owning raw pointer in large codebase; code refactoring and early return from functions.
  • dangling pointer
    • => An owning pointer is still being used after the object has been deleted, for instance, AType* ptr = new AType, the object is deleted, the pointer was not set nullptr and later some part of the code calls ptr->MemberFunction().
  • double free
    • => The pointer to a dynamically-allocated object was deleted more than once and was not set null or nullptr after it was deleted at the first time.
  • lack of exception safety
    • => If an exception happens after a heap-allocated object was instantiaded, the matching delete operator will not be called, therefore a memory leak will happen.
  • lack of clear ownership
    • => Ownership is the responbility to dispose a heap-allocated object and keep it alive while it is being used. Raw pointers doe not provide clear ownership, it is not possible to know if there are any other objects still using the pointer and the ones that will responsible for disposing the resource.

1.28.2 Smart Pointers from standard library

Smart pointers are classes that manages the lifetime and ownership of heap-allocated objects by wrapping raw pointers and emulating them through overloading of dereference operators (*) and (->). They intend to solve the raw owning pointers issues by making the ownership clear and providing proper resource cleanup ensuring exception safety.

Note:

  • Smart pointers should be used for polymorphic objects (objects with at least one virtual member function) or large objects that cannot fit on the stack.
  • Smart pointers should not be used for stack-allocated or static objects (global objects). They should only be used with objects that would be instantiated with new operator.

Smart Pointers in the C++ Standard

  • std::auto_ptr - Deprecated (Removed in C++17) => Replace it for std::unique_ptr
  • std::unique_ptr
    • predecessor: boost:unique_ptr
    • Features:
      • Single ownership
      • Non-copiable and movable-only.
      • No runtime overhead over raw pointers and costs less than a shared_ptr.
    • Use Cases:
      • PIMPL design pattern
      • Heap-allocated object is refered by a single parent object.
      • Factory functions that instatiates dynamically allocated polymorphic objects. In this case, it is recommeded to use unique_ptr as it is easier to convert to shared_ptr than the other way around.
  • std::shared_ptr
    • predecessor: boost::shared_ptr
    • Features: Shared ownership with reference counting.
    • Use Case:
      • More than one object need a copy of the pointer to polymorphic dynamically allocated object and also need to keep the object alive while it is being used.
  • std::weak_ptr
    • predecessor: boost::weak_ptr
    • Feature: No ownership.
    • Use Case:
      • Solves shared_ptr cyclic reference problems.

1.28.3 Other Smart Pointer Implementations

Boost:

QT Framework:

WXWidgets Smart Pointers:

Windows ATL - Active Template Library - COM (Component Object Model) Smart Pointers

VTK Smart Pointers:

Poco Framework

ARM Mbed

Misc - Found in Github

  • value_ptr - "A value semantics smart pointer for C++"
  • value-ptr-lite - "value-ptr-lite - A C++ smart-pointer with value semantics for C++98, C++11 and later in a single-file header-only library"
  • valuable - "A C++ smart-pointer with value-semantics."
  • rcu_ptr - "A special smart pointer to exchange data between threads"
  • tag_ptr - "This is a simple C++11 implementation of a tagged pointer, that allows to use the least significant bits in a pointer to save a payload, generally called tag. It does so by exploiting the alignment of types in memory."
  • any_ptr - "any_ptr and any_shared_ptr are 2 C++ containers for storing pointers to heterogeneous types that, unlike std::any, preserves normal pointer behaviour."
  • CUDA-smart-pointers

1.28.4 Documentation Links

Header File:

Smart Pointer Classes:

Helper Functions for Smart Pointer Instatitation:

Related Concepts:

1.28.5 std::unique_ptr

Single ownership, movable-only and non-copiable smart pointer.

Use Cases

  • Factory functions that instantiates dynamically allocated polymorphic objects.
  • The lifetime of the managed object depends only on the lifetime of a single object or only a single objects need a copy of the pointer, one to one relationship.
  • Pimpl design pattern.

Class template declaration

template< typename T
         ,typename Deleter = std::default_delete<T>
         >
class std::unique_ptr; 

// Array version 
template< typename T
         ,typename Deleter = std::default_delete<T>
         >
class std::unique_ptr<T [], Deleter; 

Member Function Table

Member functions of std::unique_ptr<T, Deleter>

Function signature Return Description
with parameters type  
Constructor    
unique_ptr() noexcept - Default constructor
unique_ptr(nullptr) noexcept    
unique_ptr(T* pointer) -  
… … … .. - Many others
     
Observers    
get() const noexcept T* Return pointer to managed object or nullptr if it owns no object.
     
     
Modifiers    
release() noexcept T* Deletes the managed object, release the memory allocated for it.
reset(T* ptr) void Replaces the managed object by a new one, deleting the old object.
swap(unique_ptr<T>& other) void Swaps the managed objects of two unique_ptr's
     
Operators    
operator*() const T& Dereference: Get reference to managed object
operator->() const noexcept T* Dereference: Access managed object member functions.
operator bool() const noexcept bool Returns true if there is a managed object. Used for if(ptr) { … not null …}
     

Helper functions (not member Functions):

Function signature Return Type Description
std::make_unique<T>(Args …) UP<T> Creates a new unique_ptr (preferred.)
std::allocate_shared - Same as make_unique, but with a custom allocator.
std::swap(UP<T>& lhs, UP<T>& rhs) void Swap managed objects of two unique_ptr's
operator <<(OS& os, std::unique_ptr<T>) OS& Print managed object to output stream (C++20)
     

Note: The following type synonym were used to describe the signatures in a concise way:

template <typename T> using UP = std::unique_ptr<T>; 
using OS = std::ostream;

Simplified Member Functions Signatures

template< typename T
         ,typename Deleter = std::default_delete<T>
         >
class unique_ptr
{
    // Internal raw owning pointer that points to a heap-allocated object.
    T* m_ptr = nullptr;
public:
    //----- Constructors --------------------------//
    //---------------------------------------------//
    // Default ctor
    unique_ptr();
    // Overload ctor1 - construct from a owning pointer
    unique_ptr(T* ptr);
    // Move ctor
    unique_ptr(unique_ptr&& rhs);
    // Move assignment operator deleted
    unique_ptr& operator=(unique_ptr&& r) noexcept;
    // Copy ctor deleted => Smart pointer non-copiable
    unique_ptr(unique_ptr const& rhs) = delete;
    // Copy assignment operator deleted => Smart pointer non-copiable
    unique_ptr& operator=(unique_ptr const& r) = delete;
    // Destructor
    ~unique_ptr();

    //--- Operator Overloading Member Functions ------//
    //-----------------------------------------------//
    T& operator*()  const;
    T* operator->() const noexcept;
    operator bool() const;

    //---- Ordinary Member Functions -----------------//
    //------------------------------------------------//
    T*   get()                    const noexcept;
    T*   release()                noexcept;
    void reset(T* new_object)     noexcept;
    void swap(unique_ptr<T>& rhs) noexcept;
};

Member Functions in Detail

  • Default constructor - initialized to nullptr.
std::unique_ptr<Type> ptr;
  • Constructors:
std::unique_ptr<Type> ptr(new Type{args ...});
  • std::make_unique - alternaitve to constructor
  • Intialization through helper function std::make_unique (C++14)
    • Note: It is not recomended to initialized a smart pointer directly through constructor.

Allocating single object:

// Args are constructor arguments.
std::unique_ptr<Type> ptr = std::make_unique<Type>(args, ....);

Allocating array:

std::unique_ptr<Type[]> ptr = std::make_unique<Type[]>();

Or:

auto ptr = std::make_unique<Type>(args, ....);

Definition:

template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args &&... args) 
{
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
  • Move constructor:
std::unique_ptr<Type> old_ptr(new Type{args ...});

// Move ctor initialized from an old pointer. 
std::unique_ptr<Type> ptr{std::move(old_ptr)};
  • Move assignment.
std::unique_ptr<Type> old_ptr(new Type{args ...});

std::unique_ptr<Type> ptr;

ptr = std::move(old_ptr);
  • Delete pointed object, releasing its memory.
auto ptr = std::make_unique<Type>(args, ....);   

ptr.reset(); // Delete heap-allocated object releasing memory and
             // resetting the pointer.
  • Check whether the pointer is null or not.
auto ptr = std::make_unique<Type>(args, ....);   

if(ptr) {
   std::puts("Pointer is null");
} else {
   std::puts("Pointer is not null");
}

if(ptr == nullptr) {
   std::puts("Pointer is null");
} else {
   std::puts("Pointer is not null");
}

1.28.6 std::shared_ptr

shared_ptr is a smart pointer that provides shared ownership semantics and memory management through reference counting. This class contains two parts, a pointer to the managed object (heap-allocated object) and a heap-allocated control block which is shared by all smart pointers owning the same object and contains a reference counter.

Whenever a copy of the smart happens, the reference counter in the shared control block is incremented by one. Whenever any smart pointer, that manages a common object, is deleted when it goes out scope, the reference counter is decremented by one. When reference counter reaches zero, the owned object and the control block are disposed.

Use cases:

  • Emulate the experience of a language with garbage collection.
  • Polymorphic object that will be referred by many other objects, in other words, many other objects need a copy of the pointer to the polymorphic object.
  • Instantiation of large objects that cannot fit in the stack.

Header

Documentation

Simplified Member Functions of std::shared_ptr<T>

Member function Return Description
  type  
Most relevant constructors    
shared_ptr()   Default constructor, initialize with nullptr.
shared_ptr( std::nullptr_t )    
shared_ptr(T* )   Initialize with new keyword.
std::shared_ptr(std::unique_ptr<T>&& r)   Initialize with a unique_ptr R-value.
     
Observers    
get() const T* Return pointer to owned object.
use_count() long Value of reference counter
     
operator bool() bool Checks whether the pointer is not null. if(ptr) { … }
operator*() const T& Dereference operator. Return reference to owned object.
operator->() const T* Dereference operator. Access member function of managed object.
     
Modifier    
reset() void Replace the current managed object by this pointer by nullptr.
reset(T* ptr) void Replace the current managed object by this pointer by ptr.
     

Note:

  • use_count() returns the number of shared_ptr managing the same heap-allocated object, in other words, it returns the value of the reference counter. When it reaches zero, the shared_ptr object owns no object or does not manage any object.
  • reset() and reset(T*) members only deletes the current managed object, if there are any other std::shared_ptr using it.

Simplified Member Functions Signature

template<typename T>
class shared_ptr
{
    // ..... omit private part  ... //
public:

    //----- Constructors --------------------------//
    //---------------------------------------------//
    // Default ctor
    shared_ptr();
    // Overload ctor1 - construct from a owning pointer
    shared_ptr(T* ptr);

    // Move ctor
    shared_ptr(shared_ptr&& rhs);
    // Move assignment operator
    shared_ptr& operator=(shared_ptr&& r);
    // Copy ctor
    shared_ptr(shared_ptr const& rhs);
    // Copy assignment operator
    shared_ptr& operator=(shared_ptr const& r);
    // Destructor
    ~shared_ptr();

    //--- Operator Overloading Member Functions ------//
    //-----------------------------------------------//
    T& operator*()  const;
    T* operator->() const noexcept;    
    operator bool() const;

    //---- Ordinary Member Functions -----------------//
    //------------------------------------------------//

    long  use_count()              const noexcept;
    T*    get()                    const noexcept;
    T*    release()                noexcept;
    void  reset(T* new_object)     noexcept;
    void  swap(shared_ptr<T>& rhs) noexcept;
};

Non Member Functions

template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );   // Since C++11 

template<class T>
shared_ptr<T> make_shared( std::size_t N );   // Since C++20 

 // ... many other operators ... // 

template< class T >
bool operator==( std::nullptr_t lhs, const shared_ptr<T>& rhs ) noexcept;

template < class T, class U > 
bool operator==( const shared_ptr<T>& lhs, const shared_ptr<U>& rhs ) noexcept;

Basics - How it works

  • When a shared_ptr owns not object or is default initialized or initialized with nullptr, the reference counter is set to zero.
// Reference counter is zero 
std::shared_ptr<AClass> ptr1;
// .use_counter => Return the value of the reference counter.
assert(ptr.use_counter() == 0) ; 

// Reference counter is zero, the ptr2 object owns anything
std::shared_ptr<AClass> ptr2 = nullptr;
assert(ptr2.use_counter() == 0) ; 

// Reference counter is zero 
std::shared_ptr<AClass> ptr2{nullptr};
assert(ptr2.use_counter() == 0) ; 
  • When a shared_ptr is initialized (not default initialized, copy or move initialization) with new keyword or std::make_shared, the reference counter is set to 1, the member function use_count returns 1.
class AClass{ 
public:
   ... ... ... 
   void method1(){ ... .}
   void method2(){ .... .. }
}; 

// Not: recommended (AVOID)
std::shared_ptr<AClass> cls(new AClass>(arg0, arg1, arg2 ...));
assert(ptr.use_count() == 1);

// Call methods just like it would be with an ordinary pointer 
ptr->method1(); 
ptr->method2(); 

// Get a reference to the object: 
AClass ref& = *ptr;

// Best 
std::shared_ptr<AClass> cls =  std::make_shared<AClass>(arg0, arg1, arg2 ...);
assert(ptr.use_count() == 1);

// Best 
auto cls =  std::make_shared<AClass>(arg0, arg1, arg2 ...);
assert(ptr.use_count() == 1);
  • When a copy of a shared_ptr object is performed, the reference counter is incremented and when a copy object goes out of scope, the reference counter is decremented.
std::shared_ptr<AClass> cls =  std::make_shared<AClass>(arg0, arg1, arg2 ...);
assert(ptr.use_count() == 1);

cls->CallMethod1OfAClass();

{ 
   // Copy constructor of std::shared_ptr<T> called 
   // => Incremente reference counter by 1 
   auto copy1 =  cls; 
   assert(ptr.use_count() == 2);   
   assert(copy1.use_count() == 2);   

  {
     std::shared_ptr<AClass> copy2; 
     assert(copy2.use_count() == 0);

     // Call move assignment operator of std::shared<T> 
     // Increment reference counter. 
     copy2 = cls;
     assert(copy2.use_count() == 3);
  }
  // Object: copy2 => Destroyed here, decremente ref counter by 1 
  assert(cls.use_count() == 2);
}
// Object copy1 destroyed here (end of its scope), decremente 
// counter by one. 
assert(cls.use_count() == 1);  

// Decremente control block by 1, when it reaches zero, 
// the control block and the managed object are destroyed.
cls = nullptr;
assert(cls.use_count() == 0);  
// Or: 
cls.reset(); 
assert(cls.use_count() == 0);  
  • The shared managed object and the control block are only disposed when the number of shared_ptr objects owning the managed objects is zero, in other words, the reference counter equal to zero.
auto cls =  std::make_shared<AClass>(arg0, arg1, arg2 ...);
assert(ptr.use_count() == 1);

std::shared_ptr<AClass> copy1{cls}; 
assert(cls.use_count() == 2);
assert(copy1.use_count() == 2);

// Reference counter decremented and cls set nullptr. 
// However, the shared control block and the managed object 
// were not deleted as they are still used by object 'copy1'. 
cls = nullptr; 
assert(cls.use_count() == 0);
assert(copy1.use_count() == 1);

// Reference counter decremented, now it reaches zero and both the
// control block and the managed (heap-allocated) object are deleted
// as they are no longer used by any other shared_ptr.
copy1 = nullptr;
assert(copy.use_count() == 0);

1.28.7 Best practices and things to avoid for std::shared_ptr

Initialization of shared_ptr

Instead of:

std::shared_ptr<SomeType> ptr{new SomeType(arg0, arg1, ..., argN)};

Better:

std::shared_ptr<SomeType> ptr = std::make_shared<SomeType>(arg0, arg1, ..., argN);

Or even better:

auto ptr = std::make_shared<SomeType>(arg0, arg1, ..., argN);

Polymorphic objects and containers

class IBase(){
public:
   virtual void method1() = 0;
   ~IInterface() = default;
};

class ClassA: public IBase {
     ... ... 
}; 

class ClassB: public IBase {
    .. ... ...
}; 
   ... ... ... .. 

Instead of:

 std::vector<IBase> dataset { new ClassA(), new ClasB(param0, param1, ...), new Class C, ... };
 dataset.push_back(new ClassA(param0, param1, ..., paramN));
 dataset.push_back(new ClassB(param0, param1, ..., paramN));

// ········ omit ··· ··· ···· 

// Dispose: 
for(auto ptr: dataset) { delete ptr; }

Better:

 std::vector<std::shared_ptr<IBase>> dataset { 
      std::make_shared<ClassA>(), 
      std::make_shared<ClassB>(param0, param1, ...),
      std::make_shared<ClassC>(), 
      ... 
};
dataset.push_back(std::make_shared<ClassA>(param0, param1, ..., paramN);
dataset.push_back(std::make_shared<ClassB>(param0, param1, ..., paramN);

for(auto cosnt& ptr: dataset){
   // iterate over a container with polymorphic objects ... 
   ... ... ... ... ... 
}

// .... Disposed automatically ... 

Or simplified with:

// Type alias 
template<typename T>
using sh = std::shared_ptr<T>;

std::vector<sh<IBase>> dataset { 
      std::make_shared<ClassA>(), 
      std::make_shared<ClassB>(param0, param1, ...),
      std::make_shared<ClassC>(), 
      ... 
}; 
 ... ... ... ... ... 

It could also be written as:

template<typename T>
using shared_vector = std::vector<std::shared_ptr<T>>;

shared_vector<T> dataset { 
      std::make_shared<ClassA>(), 
      std::make_shared<ClassB>(param0, param1, ...),
      std::make_shared<ClassC>(), 
      ... 
}; 

// ... ... ... // 

Free Functions and member parameter passing

If the function only needs to query or modify the managed object and does not need to know anything about the shared_ptr, pass by reference, const reference or by pointer in the case that null pointer is acceptable. Functions with those types of arguments will be able to work with ordinary values, dumb pointers and any type of smart pointers.

void someModifierFunction(AType& obj1)
{
   std::cout << " id = " << obj1.id() << std::endl; 
   obj1.setName(new_name); 
   obj1.display();
}

void someConstFunction(AType const& obj1)
{
   std::cout << " id = " << obj1.id() << std::endl; 
}

void somePointerFunction(AType* pobj1)
{
   if(!pobj1){
       std::cout << " Supplied null pointer"  << std::endl;
       return; 
   }
   std::cout << " id = " << pobj1->id() << std::endl; 
}

auto ptr = std::make_shared_ptr<AType>(Arg0, Arg1, ... ArgN); 

// Passing ptr to functions 
someModifierFunction(*ptr);
someConstFunction(*ptr);
somePointerFunction(ptr,get());

Avoid passing the shared pointer by value, if taking ownership, in other words, storing a copy of the pointer is not needed as the copy increments the reference counter incurring on a performance overhead.

// AVOID 
void someFunction(std::shared_ptr<T> ptr)
{
    // ... incremente atomic reference counter ... 
    // ... . .. // 
}

Only pass by reference to shared_ptr if the function needs to know about the pointer and does not need to take any ownership (storing a copy of the pointer for later reference).

void function_that_needs_to_know_about_ptr(std::shared_ptr<T> const& ptr)
{
   std::cout << " Ref count value = " << ptr.use_count() << "\n";
   std::cout << " Address = " << ptr.get() << "\n";
   std::cout << " Value returned by object's member function = " << ptr->memberFunction() << "\n";
}

Ownership

Avoid:

  • The following class needs to store a copy of a pointer to polymorphic class for later reference and it is responsible for disposing the resource during its destruction. In other words, it takes ownership of the resource.
class ClassThatTakesOwnership
{
private:
   // Pointer to heap-allocated instance of polymorphic class 
   IInterface* m_ptr;

public: 
   ClassThatTakesOwnership(IInterface* ptr): m_ptr(ptr) {  } 

   ~ClassThatTakesOwnership()
    {
       delete m_ptr; 
       m_ptr = nullptr; 
    }

   void callLater(){ 
      ptr->callMethod(); 
   }      
};

ClassThatTakesOwnership cls1{new ImplementationA};

// NO! BROKEN!
std::shared_ptr<IInterface> obj = std::make_shared<ImplementationA>(); 
// NO FLAWED! 
ClassThatTakesOwnership cls2{obj.get()};

Better:

class ClassThatTakesOwnership
{
private:
   // Pointer to heap-allocated instance of polymorphic class 
   std::shared_ptr<IInterface> m_ptr;

public: 
   ClassThatTakesOwnership(std::shared_ptr<IInterface> ptr)
     : m_ptr(std::move(ptr)) {  } 

   void callLater(){ 
      ptr->callMethod(); 
   }      
};

std::shared_ptr<IInterface> obj = std::make_shared<ImplementationA>(); 
ClassThatTakesOwnership cls1{obj};

ClassThatTakesOwnership cls2{std::make_shared<ImplementationB>()};

Factory functions

It is better to return std::unique_ptr from factory functions rather than shared_ptr as the first is easily turned into the later and opposite is more difficult.

class IInterface(){
public:
   virtual void method1() = 0;
   ~IInterface() = default;
};

class ImplementationA: public IInterface {
   void method1() override { 
      ... ... .. 
   }
}; 

class ImplementationB: public IInterface {
   void method1() override { 
      ... ... .. 
   }
}; 

auto factoryFunction(int id) -> std::unique_ptr<IInterface> 
{
   if(id == 1) { return std::make_unique<ImplementationA>(); }
   if(id == 2) { return std::make_unique<ImplementationB>(); }
   return nullptr;
}

// ----------- Usage ----------------------------
std::shared_ptr<IInterface> p_int1 = factoryFunction(1); 
std::shared_ptr<IInterface> p_int2 { factoryFunction(1) };

Polymorphism and Casting of Shared Pointers

The functions std::static_pointer_cast, std::dynamic_pointer_cast, std::const_pointer_cast and std::shared_pointer_cast from pointer_cast (cppreference) can be used for downcasting and runtime type identification of polymorphic objects.

Consider the hierarchy:

class IBase(){
public:
   virtual void method1() = 0;
   ~IInterface() = default;
};

class ClassA: public IBase {
   void method1() override { 
      ... ... .. 
   }

   void methodExclusiveOfA() { .... }
}; 

class ClassB: public IBase {
   void method1() override { 
      ... ... .. 
   }

   void methodExclusiveOfB() { ... }
}; 

Create an instance of class A:

std::shared_ptr<IBase> obj = std::make_shared<ClassA>(obj);
  • Static casting - Casting at compile time without any runtime overhead, however, if the casting is not possible, it results in undefined behavior just like what happens with static_cast for pointers.
std::shared_ptr<A> ptr_a = std::static_pointer_cast<A>(obj);
if(ptr_a) {
   ptr_a->method1();
   ptr_a->methodExclusiveofA(); 
}

// **** WARNING!! UNDEFINED BEHAVIOR!! *****
std::shared_ptr<A> ptr_b = std::static_pointer_cast<B>(obj);
  • Dynamic Casting - RTTI - Runtime Type Identification, if the casting fails, the function std::dynamic_pointer_cast returns a null pointer just like dynamic_cast<T> operator.
// Increment reference counter by 1 if successful 
std::shared_ptr<A> ptr_a = std::dynamic_pointer_cast<A>(obj);
assert( ptr_a != nullptr);
if(ptr_a) {
   ptr_a->method1();
   ptr_a->methodExclusiveofA(); 
}

auto ptr_b = std::dynamic_pointer_cast<B>(obj);
assert( ptr_b == nullptr);
if(ptr_b){
   ptr_b->method1();
   ptr_b->methodExclusiveofB(); 
}

1.28.8 Example: unique_ptr

Example:

  • A car object has only one engine, multiple cars cannot have the same engine and the engine is destroyed when the car object is destroyed.
  • The engine is polymorphic object implementing the interface IEngine.
  • The car object can replace the engine at runtime.

Full code:

Headers:

#include <iostream>
#include <string>

#include <type_traits>

// Provide std::unique_ptr and std::shared_ptr
#include <memory>

Interface: IEngine

// Car Engine Interface
class IEngine{
public:
    virtual std::string type() const = 0;
    virtual void        run()  = 0;
    // virtual void        shutdown() = 0;
    virtual ~IEngine() = default;
};

Class DieselEngine implemeting the interface IEngine.

class DieselEngine: public IEngine
{
public:
    DieselEngine()
    {
        std::puts(" [INFO] Diesel Engine created.");
    }

    ~DieselEngine()
    {
        std::puts(" [INFO] Diesel Engine deleted.");
    }

    std::string type() const override
    {
        return "diesel engine";
    }

    void run() override
    {
        std::puts(" Running a super powerful diesel Engine!!");
    }
};

Class ElectricEngine implemeting the interface IEngine:

class ElectricEngine: public IEngine
{
public:
    ElectricEngine()
    {
        std::puts(" [INFO] Electrict Engine created.");
    }

    ~ElectricEngine()
    {
        std::puts(" [INFO] Electric Engine deleted.");
    }

    std::string type() const override
    {
        return "electric engine";
    }

    void run() override
    {
        std::puts(" =>> Running a silent and clean electric engine!!");
    }
};

Class SomeCar that contains an engine object implemeting the IEngine interface.

class SomeCar{
private:
    std::unique_ptr<IEngine> m_engine;
public:

    SomeCar(): m_engine(nullptr) { }

    // Constructor: Dependency injection
    // => the dependency is supplied and instantiated
    // by an external code rather than being instantiated by this class.
    SomeCar(IEngine* obj): m_engine(obj){ }

    // Unique_ptr cannot be copied, they only can be moved.
    // The std::move, transfers the ownership of the supplied std::unique_ptr
    // to this class.
    SomeCar(std::unique_ptr<IEngine> engine)
        : m_engine(std::move(engine))
    {
    }

    void run_engine(){
        std::puts(" [INFO] Run car engine.");
        // Unique_ptr can be used just as an ordinary pointer
        m_engine->run();
    }

    // Set a new engine.
    void set_engine(IEngine* engine)
    {
        // Disposes the old engine and sets a new one
        m_engine.reset(engine);
    }

    void set_engine(std::unique_ptr<IEngine> engine)
    {
        // Calls move assingment operator disposing the old engine
        m_engine  = std::move(engine);
    }

    std::string engine_type() {
        return m_engine->type();
    }

    void remove_engine() {
        // Delete wrapped heap-allocated object
        m_engine.release();
    }

    // Check if the car has an engine
    bool has_engine(){
        return m_engine != nullptr;
    }

    ~SomeCar(){
        std::puts(" [INFO] object of class SomeCar disposed. OK");
    }

};

Factory function:

std::unique_ptr<IEngine>
factoryFunction(char code)
{
    if(code == 'e') { return std::make_unique<ElectricEngine>(); }
    if(code == 'd') { return std::make_unique<DieselEngine>(); }
    return nullptr;
}

Main Function - int main()

  • Main Function - Experiment 0
std::cout << "\n ***** EXPERIENT 0 === Type Traits *********************\n";
std::cout << "---------------------------------------------------------------\n";
std::cout << std::boolalpha;
{

    std::cout << " is_abstract<IEngine>() = "
              << std::is_abstract<IEngine>() << "\n";

    std::cout << " is_abstract<ElectricEngine>() = "
              << std::is_abstract<ElectricEngine>() << "\n";

    std::cout << " is_fundamental<ElectricEngine>() = "
              << std::is_fundamental<ElectricEngine>() << "\n";

    std::cout << " is_polymorphic<ElectricEngine>() = "
              << std::is_polymorphic<ElectricEngine>() << "\n";

    std::cout << " is_trivially_copiable<ElectricEngine() = "
              << std::is_trivially_copyable<ElectricEngine>() << "\n";

    std::cout << " is_trivially_move_constructible<ElectricEngine() = "
              << std::is_trivially_move_constructible<ElectricEngine>() << "\n";

}

Output:

***** EXPERIENT 0 === Type Traits *********************
---------------------------------------------------------------
 is_abstract<IEngine>() = true
 is_abstract<ElectricEngine>() = false
 is_fundamental<ElectricEngine>() = false
 is_polymorphic<ElectricEngine>() = true
 is_trivially_copiable<ElectricEngine() = false
 is_trivially_move_constructible<ElectricEngine() = false
  • Main function - Experiment 1
std::cout << "\n ***** EXPERIMENT 1 = Instatiate a car with new operator ****\n ";
std::cout << "---------------------------------------------------------------\n";
{
    // Note: note recommended using 'new', instead use std::make_unique
    SomeCar car1(new DieselEngine);
    std::cout << " car1.has_engine() = " << car1.has_engine() << "\n";
    std::cout << " car1.engine_type() = " << car1.engine_type() << "\n";
    car1.run_engine();

    // Car and engine deleted at the end of this scope or at this bracket.
}

Output:

***** EXPERIMENT 1 = Instatiate a car with new operator ****
---------------------------------------------------------------
[INFO] Diesel Engine created.
car1.has_engine() = true
car1.engine_type() = diesel engine
[INFO] Run car engine.
Running a super powerful diesel Engine!!
[INFO] object of class SomeCar disposed. OK
[INFO] Diesel Engine deleted.
  • Main function - Experiment 2
std::cout << "\n **** EXPERIMENT 2 = Instatiate a car with an exising unique_ptr ****\n ";
std::cout << "---------------------------------------------------------------\n";
{
    // Not recommended using new!
    std::unique_ptr<IEngine> engineA(new ElectricEngine);

    // Note: engineA is not copiable, can only be moved
    SomeCar carA(std::move(engineA));
    std::cout << " carA.has_engine() = " << carA.has_engine() << "\n";
    std::cout << " carA.engine_type() = " << carA.engine_type() << "\n";
    carA.run_engine();

    // Remove engine of carA and sell it at the market
    carA.remove_engine();
    std::cout << " After removal of carA's engine." << "\n";
    std::cout << " car1.has_engine() = " << carA.has_engine() << "\n";

    std::cout << "\n After removing engine of CarA" << "\n";
    carA.set_engine(new DieselEngine);
    std::cout << " carA.engine_type() = " << carA.engine_type() << "\n";
    carA.run_engine();
}

Output:

**** EXPERIMENT 2 = Instatiate a car with an exising unique_ptr ****
 ---------------------------------------------------------------
 [INFO] Electrict Engine created.
 [INFO] Called SomeCar L-value reference constructor
 carA.has_engine() = true
 carA.engine_type() = electric engine
 [INFO] Run car engine.
 =>> Running a silent and clean electric engine!!
 After removal of carA's engine.
 car1.has_engine() = false

 After removing engine of CarA
 [INFO] Diesel Engine created.
 carA.engine_type() = diesel engine
 [INFO] Run car engine.
 Running a super powerful diesel Engine!!
 [INFO] object of class SomeCar disposed. OK
 [INFO] Diesel Engine deleted.
  • Main function - Experiment 3
std::cout << "\n **** EXPERIMENT 3 = Using std::make_unique ****\n ";
std::cout << "---------------------------------------------------------------\n";
{
    SomeCar carA(std::make_unique<DieselEngine>());
    std::cout << " carA.has_engine() = " << carA.has_engine() << "\n";
    std::cout << " carA.engine_type() = " << carA.engine_type() << "\n";
    carA.run_engine();


    std::cout << "\n [TRACE] After setting a new engine for CarA" << "\n";
    auto engine = std::make_unique<ElectricEngine>();
    std::cout << " ?? engine == nullptr " << (engine == nullptr) << std::endl;

    carA.set_engine(std::move(engine));

    std::cout << " carA.engine_type() = " << carA.engine_type() << "\n";
    carA.run_engine();

    std::cout << "\n [TRACE] After setting a new engine again for CarA" << "\n";
    carA.set_engine( std::make_unique<DieselEngine>() );
    std::cout << " carA.engine_type() = " << carA.engine_type() << "\n";
    carA.run_engine();

}

Output:

**** EXPERIMENT 3 = Using std::make_unique ****
---------------------------------------------------------------
[INFO] Diesel Engine created.
[INFO] Called SomeCar R-value reference constructor
carA.has_engine() = true
carA.engine_type() = diesel engine
[INFO] Run car engine.
Running a super powerful diesel Engine!!

[TRACE] After setting a new engine for CarA
[INFO] Electrict Engine created.
?? engine == nullptr false
[INFO] Diesel Engine deleted.
carA.engine_type() = electric engine
[INFO] Run car engine.
=>> Running a silent and clean electric engine!!

[TRACE] After setting a new engine again for CarA
[INFO] Diesel Engine created.
[INFO] Electric Engine deleted.
carA.engine_type() = diesel engine
[INFO] Run car engine.
Running a super powerful diesel Engine!!
[INFO] object of class SomeCar disposed. OK
[INFO] Diesel Engine deleted.
  • Main function - Experiment 4
std::cout << "\n **** EXPERIMENT 4 = Factory Function ****\n ";
std::cout << "---------------------------------------------------------------\n";
{
    SomeCar car;
    auto engine1 = factoryFunction('x');

    // If the pointer is not nullptr (null) evalutes to true
    if(engine1){
        std::cout << " Engine created from factory function. Ok. \n";
    } else {
        car.set_engine(std::move(engine1));
    }
    std::cout << " ??? car.has_engine() = " << car.has_engine() << std::endl;

    std::cout << "\n Set engine to DIESEL engine.\n";
    car.set_engine(factoryFunction('d'));
    std::cout << " ??? car.has_engine() = " << car.has_engine() << std::endl;
    std::cout << " Engine type = " << car.engine_type() << "\n";
    car.run_engine();

    std::cout << "\n Set engine to ELECTRIC engine.\n";
    car.set_engine(factoryFunction('e'));
    std::cout << " ??? car.has_engine() = " << car.has_engine() << std::endl;
    std::cout << " Engine type = " << car.engine_type() << "\n";
    car.run_engine();
}

Output:

**** EXPERIMENT 4 = Factory Function ****
---------------------------------------------------------------
??? car.has_engine() = false

Set engine to DIESEL engine.
[INFO] Diesel Engine created.
??? car.has_engine() = true
Engine type = diesel engine
[INFO] Run car engine.
Running a super powerful diesel Engine!!

Set engine to ELECTRIC engine.
[INFO] Electrict Engine created.
[INFO] Diesel Engine deleted.
??? car.has_engine() = true
Engine type = electric engine
[INFO] Run car engine.
=>> Running a silent and clean electric engine!!
[INFO] object of class SomeCar disposed. OK
[INFO] Electric Engine deleted.

1.28.9 Example: shared_ptr

Full code:

Build:

# Build 
$ g++ shared_ptr1.cpp -o out.bin -std=c++1z -g -O0 -Wall -Wextra 

# Run 
./out.bin 

Headers:

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

#include <cassert> // assert (assertions)
#include <memory> // Provide: std::shared_ptr

Interface: IMediaAsset

// Media asset interface => generic specification for an media asset.
// Note: A struct is just a class with everything public by default.
struct IMediaAsset{
    virtual std::string type() const = 0;
    virtual std::string name() const = 0;
    virtual void play()              = 0;

    /** Always add a default destructor. */
    virtual ~IMediaAsset() = default;
};

Class: MusicAsset

class MusicAsset: public IMediaAsset
{
    std::string m_name;
public:

    MusicAsset(std::string name):
        m_name(std::move(name))
    {
        std::printf(" [TRACE] CTOR - Music object => name = %s created. \n "
                    , m_name.c_str());
    }

    MusicAsset(): MusicAsset("unnamed-music") { }

    ~MusicAsset()
    {
        std::printf(" [TRACE] DTOR - Music object => name = %s destroyed. \n "
                    , m_name.c_str());
    }

    // Override keyword is optional, but it is recommended in C++11
    std::string type() const override
    {
        return "music";
    }

    std::string name() const override
    {
        return m_name;
    }

    void play() override
    {
        std::cout << " [INFO] Playing music: " << m_name << std::endl;
    }

    void setVolumePercent(unsigned int volume) {
        std::cout << " [INFO] Volume of music <"
                  << m_name << "> set to "
                  << volume << "%\n";
    }

};

Class PictureAsset:

class PictureAsset: public IMediaAsset
{
    std::string m_name;
public:

    PictureAsset(std::string name):
        m_name(std::move(name))
    {
        std::printf(" [TRACE] CTOR - Picture object => name = %s created. \n "
                   , m_name.c_str());
    }

    PictureAsset(): PictureAsset("unnamed-picture") { }

    ~PictureAsset()
    {
        std::printf(" [TRACE] DTOR - Picture object => name = %s destroyed. \n "
                    , m_name.c_str());
    }

    // Override keyword is optional, but it is recommended in C++11
    std::string type() const override
    {
        return "picture";
    }

    std::string name() const override
    {
        return m_name;
    }

    void play() override
    {
        std::cout << " [INFO] Show picture : " << m_name << std::endl;
    }

    void resizeInPercent(unsigned int percent) {
        std::cout << " [INFO] Picture<"
                  << m_name << "> resized "
                  << percent << "%\n";
    }


};

Shared pointer type alias:

// Type alias for shared_ptr
template<typename T>
using sh = std::shared_ptr<T>;

Factory function:

/* For the factory function use std::unique_ptr instead of shared_ptr as
 * it is easier to convert unique_ptr to shared_ptr than the other way around.
 */
std::unique_ptr<IMediaAsset>
factoryFunction(int id)
{
    if(id == 1) { return std::make_unique<MusicAsset>("Fur Elise"); }
    if(id == 2) { return std::make_unique<PictureAsset>("São Paulo's Skyline"); }
    if(id == 3) { return std::make_unique<MusicAsset>("Baorque music"); }
    if(id == 4) { return std::make_unique<PictureAsset>("Tokyo skyline"); }
    return nullptr;
}
  • main() Function - Experiment 1
std::cout << std::boolalpha;

//=============== EXPERIMENT 1 ===============================================//
std::puts("\n ****** =>>>> EXPERIMENT 1 - Reference counter test **************\n");

// Not recommended using new => Instead use std::make_shared
// When the object is created, the reference count is set to 1.
std::shared_ptr<PictureAsset> pic1(new PictureAsset("Desert sunshine"));
assert( pic1.use_count() == 1);

std::cout << " => Type of object pic1 = " << pic1->type()
          << "  ; name = " << pic1->name()
          << std::endl;

std::cout << " => Address of pic1 managed object = " << pic1.get() << std::endl;
std::cout << " => Value of ref. counter of pic1 = " << pic1.use_count() << std::endl;


{
    std::puts(" >> [TRACE] Before entering local scope <<=== \n");

    // Increments reference counter by 1 (set to 2) => .use_count() returns 2
    std::shared_ptr<PictureAsset> pic1_copyA = pic1;
    assert(pic1_copyA.use_count() == 2);
    assert(pic1.use_count() == 2);
    std::printf(" [INFO] Reference count of pic1_copyA = %ld \n", pic1_copyA.use_count());
    std::printf(" pic1_copyA->name() = %s \n", pic1_copyA->name().c_str());


    // Increments reference counter by 1 (set to 3)
    auto pic1_copyB = pic1;
    assert(pic1.use_count() == 3);
    assert(pic1_copyB.use_count() == 3);
    // .use_count() returns 3
    std::printf(" [INFO] Reference count of pic1_copyB = %ld \n", pic1_copyA.use_count());
    std::printf(" pic1_copyB->name() = %s \n", pic1_copyB->name().c_str());

    /** Objdcts pic1_copyA and pic1_copyB are destroyed,
     * then each one decrements the reference counter by 1
     */
    std::puts(" >> [TRACE] Levaing  local scope <<=== ");
}

// .use_count returns 1 as the two copies were destroyed.
std::printf(" [INFO] Before reset(). Reference count of pic1 = %ld \n", pic1.use_count());

assert(pic1.use_count() == 1);
// Decrements reference counter of control block by 1.
// The owned object and the control block are deleted when the counter is zero.
// When the counter reaches zero, the internal pointer is set to nullptr.
pic1.reset();
assert(pic1.use_count() == 0);

std::printf(" [INFO] After reset(). Reference count of pic1 = %ld \n", pic1.use_count());

std::cout << " ?? Is pic1 nullptr => (pic1 == nullptr) : "
          << (pic1  == nullptr)
          << std::endl;

std::cout << " ?? Does shared_ptr pic1 own an object => (pic1 != nullptr) : "
          << (pic1  != nullptr)
          << std::endl;

Output:

****** =>>>> EXPERIMENT 1 - Reference counter test **************

 [TRACE] CTOR - Picture object => name = Desert sunshine created. 
  => Type of object pic1 = picture  ; name = Desert sunshine
 => Address of pic1 managed object = 0x1b73e80
 => Value of ref. counter of pic1 = 1
 >> [TRACE] Before entering local scope <<=== 

 [INFO] Reference count of pic1_copyA = 2 
 pic1_copyA->name() = Desert sunshine 
 [INFO] Reference count of pic1_copyB = 3 
 pic1_copyB->name() = Desert sunshine 
 >> [TRACE] Levaing  local scope <<=== 
 [INFO] Before reset(). Reference count of pic1 = 1 
 [TRACE] DTOR - Picture object => name = Desert sunshine destroyed. 
  [INFO] After reset(). Reference count of pic1 = 0 
 ?? Is pic1 nullptr => (pic1 == nullptr) : true
 ?? Does shared_ptr pic1 own an object => (pic1 != nullptr) : false

  • main() Function - Experiment 2
//======= EXPERIMENT 2 ===>> Polymorphism ======================================//
std::puts("\n ****** =>>>> EXPERIMENT 2 - Polymorphism test **************\n");

std::printf(" \n --- Experiment 2.A - musicA object \n");

// Default initialized to nullptr (reference counter is zero).
std::shared_ptr<IMediaAsset> media1;
assert(media1.use_count() == 0);

if(!media1){
    std::cout << " [TRACE] Media pointer owns no object, it is nullptr" << std::endl;
} else {
    std::cout << " [TRACE] Media pointer owns an object, it is not nullptr" << std::endl;
}
std::printf(" [INFO] Ref count of media1 = %ld \n", media1.use_count());

// Best way to instantiante a polymorphic object
// Type: std::shared_ptr<MusicAsset>
auto musicA = std::make_shared<MusicAsset>("Fur Elise");
std::printf(" [INFO] Ref count of musicA = %ld \n", musicA.use_count());

// Polymorphism: the pointer to a derived class
// can be assigned to a pointer of the base class.
media1 = musicA;
std::printf(" [INFO] Ref count of musicA = %ld \n", musicA.use_count());
std::printf(" [INFO] Ref count of medai1 = %ld \n", media1.use_count());

std::printf(" \n --- Experiment 2.B - pictA object \n");
{


    // Type: std::shared_ptr<PictureAsset>
    auto pictA = std::make_shared<PictureAsset>("Canyon sunset");
    std::printf(" [INFO] Before assigning: Ref count of pictA = %ld \n"
                , pictA.use_count());

    std::printf(" => pictA->type() = '%s' - pictA->name() = %s  "
                , pictA->type().c_str(), pictA->name().c_str() );

    media1 = pictA;
    std::printf(" [INFO] After assigning: Ref count of pictA = %ld \n"
                , musicA.use_count());

    std::printf(" => media1->type() = '%s' - media1->name() = %s  \n"
                , media1->type().c_str(), media1->name().c_str() );

    // Object (shared pointer) pictA destroyed here
    // , but not the owned object "Canyon sunset" as it also owned by 'media1' pointer
}

std::printf(" \n --- Experiment 2.C - After end of local scope \n");

std::shared_ptr<IMediaAsset> media2 = media1;
std::printf(" [INFO] Ref count of media1 = %ld \n", media1.use_count());
std::printf(" [INFO] Ref count of media1 = %ld \n", media2.use_count());

media1 = nullptr;
std::printf(" [INFO] Ater setting media1 to null => Ref count of media1 = %ld \n"
            , media1.use_count());
std::printf(" [INFO] Ater setting media2 to null => Ref count of media2 = %ld \n"
            , media2.use_count());

std::printf(" Before setting media2 to nullptr => media2->type() = '%s' - media2->name() = %s  \n"
            , media2->type().c_str(), media2->name().c_str() );

// Reference count set to zero => The object "Canyon sunset" is destroyed
// as any pointer no longer owns it.
media2 = nullptr;

std::cout << " After setting media2 to nullptr => ?? Is media2 nullptr = "
          << (media2 == nullptr) << std::endl;

Output:

****** =>>>> EXPERIMENT 2 - Polymorphism test **************


 --- Experiment 2.A - musicA object 
 [TRACE] Media pointer owns no object, it is nullptr
 [INFO] Ref count of media1 = 0 
 [TRACE] CTOR - Music object => name = Fur Elise created. 
  [INFO] Ref count of musicA = 1 
 [INFO] Ref count of musicA = 2 
 [INFO] Ref count of medai1 = 2 

 --- Experiment 2.B - pictA object 
 [TRACE] CTOR - Picture object => name = Canyon sunset created. 
  [INFO] Before assigning: Ref count of pictA = 1 
 => pictA->type() = 'picture' - pictA->name() = Canyon sunset   [INFO] After assigning: Ref count of pictA = 1 
 => media1->type() = 'picture' - media1->name() = Canyon sunset  

 --- Experiment 2.C - After end of local scope 
 [INFO] Ref count of media1 = 2 
 [INFO] Ref count of media1 = 2 
 [INFO] Ater setting media1 to null => Ref count of media1 = 0 
 [INFO] Ater setting media2 to null => Ref count of media2 = 1 
 Before setting media2 to nullptr => media2->type() = 'picture' - media2->name() = Canyon sunset  
 [TRACE] DTOR - Picture object => name = Canyon sunset destroyed. 
  After setting media2 to nullptr => ?? Is media2 nullptr = true
  • Experiment 3
//=============== EXPERIMENT 3 - Polymorphism and pointer casting ===========//
std::puts("\n ****** =>>>> EXPERIMENT 3 - Polymorphism and casting ***********\n");

std::shared_ptr<IMediaAsset> asset1 = std::make_shared<MusicAsset>("Blues");


// Downcast object at runtime (RTTI) checking whetehr it
// is a Music asset taking its ownership.
{
    // RTTI Runtime - Type information
    std::shared_ptr<PictureAsset> asset_is_music = std::dynamic_pointer_cast<PictureAsset>(asset1);
    if(asset_is_music){
        std::cout << " =>>> Asset type is music OK" << std::endl;
        std::printf(" Object Type = %s - object name = %s\n"
                    , asset_is_music->type().c_str()
                        , asset_is_music->name().c_str());

    } else {
        std::cout << " =>> Asset type is not music. " << std::endl;
    }

}

// Static casting object at runtime => IF the casting is wrong, it results in
// undefined behavior!!

std::cout << "\n ---- Before changing pointed object " << std::endl;
asset1 = factoryFunction(2);
std::cout << "\n ---- After changing pointed object " << std::endl;

if(asset1->type() == "picture")
{
    // Warning: if the casting does not match, results in undefined behavior!!
    auto asset_picture = std::static_pointer_cast<PictureAsset>(asset1);

    std::cout << "\n =>>> Asset type is picture OK" << std::endl;
    std::printf(" Object Type = %s - object name = %s\n"
                , asset_picture->type().c_str()
                    , asset_picture->name().c_str());

    asset_picture->resizeInPercent(40);
}

Output:

****** =>>>> EXPERIMENT 3 - Polymorphism and casting ***********

 [TRACE] CTOR - Music object => name = Blues created. 
  =>> Asset type is not music. 

 ---- Before changing pointed object 
 [TRACE] CTOR - Picture object => name = São Paulo's Skyline created. 
  [TRACE] DTOR - Music object => name = Blues destroyed. 

 ---- After changing pointed object 

 =>>> Asset type is picture OK
 Object Type = picture - object name = São Paulo's Skyline
 [INFO] Picture<São Paulo's Skyline> resized 40%
  • Experiment 4
//=============== EXPERIMENT 4 - Polymorphism and containers ================//
std::puts("\n ****** =>>>> EXPERIMENT 4 - Polymorphioc Objects and Containers *****\n");

auto objA = std::make_shared<MusicAsset>("Some medieval music");
std::shared_ptr<IMediaAsset> objB = factoryFunction(2);

std::cout << "\n --- Before filling 'collection' " << std::endl;

// Polymorphism
std::vector<sh<IMediaAsset>> collection =
    {
        std::make_shared<PictureAsset>("Max turbo power diesel engine")
      , std::make_shared<MusicAsset>("Some baroque music")
      , std::make_shared<PictureAsset>("Desert canyon")
      , factoryFunction(1)
    };

collection.push_back(objA);
collection.push_back(objB);

std::cout << " --- After filling 'collection' \n\n";

std::cout << " Ref counter of objA = " << objA.use_count() << std::endl;

std::cout << " ==== Print objects in the container ==== " << std::endl;

for(auto const& ptr: collection){
    std::cout << std::right << std::setw(15) << ptr->type()
              << std::right << "  "
              << std::left  << std::setw(35) << ptr->name()
              << std::endl;
}
std::cout << std::right;

std::cout << "\n ----- Before clearing the vector  ----- " << std::endl;

collection.clear();

std::cout << "\n ----- After clearing the vector  ----- " << std::endl;

objA = nullptr;
objB.reset();

std::puts("\n  *********** End of Main() *****************");

return 0;

Output:

****** =>>>> EXPERIMENT 4 - Polymorphioc Objects and Containers *****

 [TRACE] CTOR - Music object => name = Some medieval music created. 
  [TRACE] CTOR - Picture object => name = São Paulo's Skyline created. 

 --- Before filling 'collection' 
 [TRACE] CTOR - Picture object => name = Max turbo power diesel engine created. 
  [TRACE] CTOR - Music object => name = Some baroque music created. 
  [TRACE] CTOR - Picture object => name = Desert canyon created. 
  [TRACE] CTOR - Music object => name = Fur Elise created. 
  --- After filling 'collection' 

 Ref counter of objA = 2
 ==== Print objects in the container ==== 
        picture  Max turbo power diesel engine      
          music  Some baroque music                 
        picture  Desert canyon                      
          music  Fur Elise                          
          music  Some medieval music                
        picture  São Paulo's Skyline               

 ----- Before clearing the vector  ----- 
 [TRACE] DTOR - Picture object => name = Max turbo power diesel engine destroyed. 
  [TRACE] DTOR - Music object => name = Some baroque music destroyed. 
  [TRACE] DTOR - Picture object => name = Desert canyon destroyed. 
  [TRACE] DTOR - Music object => name = Fur Elise destroyed. 

 ----- After clearing the vector  ----- 
 [TRACE] DTOR - Music object => name = Some medieval music destroyed. 
  [TRACE] DTOR - Picture object => name = São Paulo's Skyline destroyed. 

  *********** End of Main() *****************
 [TRACE] DTOR - Picture object => name = São Paulo's Skyline destroyed. 
  [TRACE] DTOR - Music object => name = Fur Elise destroyed. 

1.28.10 Selected Codebases with std::shared_ptr

Libraries and Applications:

Computer Graphics and Game Engines:

1.28.11 Additional Reading

General:

Shared Pointers in Projects and Libraries

1.29 Exceptions and Error Handling

1.29.1 Overview

C++ has several error Handling approaches:

  • Exceptions: - try-catch and throw
    • Benefits:
      • Code Separation:
        • Separation of error handling from the computation logic.
      • Error notification
        • Forces developers to handle the error by ending the the program execution when the application is not ready to handle the error that caused the exception.
      • Easier to recover from errors and failures.
      • Stack Traces:
    • Downsides:
      • Increase of executable size - It should not be a concern anymore, since hard driver are becoming more cheaper with the passing of time.
      • According to many sources, exceptions can be a concern in real time embedded systems.
  • Error codes in global variables
    • Many C APIs handle error by setting some global variable that the calling code must check for some some error condition. For instance, the U*nix C-API indicates error by setting the global variable errno that must be checked by the calling code after after some operation that may set this flag. The downside of the this approach is that if the program doesn't check the global error flag, the program may continue its execution without notifying the user that something is wrong what can lead to runtime bugs hard to trace.
    • Downsides:
      • It is easy to forget handling global variables with error code.
      • May have multi-threading racing conditions issues.
      • Don't notify users that the program cannot handle the runtime errors.
      • Hard to trace and debug.
  • Error codes as return value:
    • A function can provide error notification by returning an error code to the caller. For instance, a function which downloads a file could return 1 for successful download, 2 for DNS resolution error, 3 for network error and so on.
    • Variations:
      • Return an error code such as a bitmask or enumeration. Example: HRESULT type is returned type by many Windows COM API for indicating error
      • Return a null pointer to indicate failure or the absence of an object, that it was no possible to find an object or allocate memory. Example: C++ new operator with nothrow,
        • Example:
        • Base* ptr = new (nothrow ) Derived;
        • if(ptr){ .. do something …} else { std::puts("Error: … "); )}
      • Return an error code or error flag from class methods. This approach is used by C++ streams std::iosbase and its derived classes, std::cin, std::istream and so on., bool std::iosbase.good(), bool std::iosbase.fail().
  • User supplied callback function
    • The client code supplies a callback function which is called when an error happens.
  • Using 'either' type
  • std::optional<T> - C++17 Maybe or Optional type.

Exception Headers

Exceptions Good Practices:

  • Assertions: should not be used for argument validation since they can be disabled during compilation or on release builds.
  • Exceptions should be used on objects' constructors in order to avoid letting the object in an invalid state. => Fail Fast principle.
  • Destructors should not throw exceptions as it invokers terminates() shutting down the program.
  • DO NOT: catch all exceptions. Only exceptions that can be handled should be caught.

C++ Stack Terminology

  • Exception Specification
  • Stack Unwinding
  • Exception-safety
  • RAII - Resource Aquisition is Resource Initialization
  • Smart Pointers

Functions related to exceptions

Standard Library Exception defined in header file <exception>

Exception Description
std::exception An exception and parent class of all the standard C++ exceptions.
std::runtime_error An exception that theoretically can not be detected by reading the code.
std::logic_error An exception that theoretically can be detected by reading the code.
std::domain_error This is an exception thrown when a mathematically invalid domain is used
std::invalid_argument This is thrown due to invalid arguments.
std::bad_alloc This can be thrown by new.
std::bad_cast This can be thrown by dynamic_cast.
std::bad_exception This is useful device to handle unexpected exceptions in a C++ program
std::bad_typeid This can be thrown by typeid.
std::length_error This is thrown when a too big std::string is created
std::out_of_range This can be thrown by the at method from for example a std::vector and std::bitset<>::operator[]().
std::overflow_error This is thrown if a mathematical overflow occurs.
std::range_error This is occured when you try to store a value which is out of range.
std::underflow_error This is thrown if a mathematical underflow occurs.

References:

C++ Documentation:

Misc:

Best:

1.29.2 Exception Basics

C++ can throw any type as exception:

>> throw 10
Error in <TRint::HandleTermInput()>: Exception caught!
>> 
>> throw "hello world"
Error in <TRint::HandleTermInput()>: Exception caught!
>> 
>> throw 34.212
Error in <TRint::HandleTermInput()>: Exception caught!
>> 
>> throw std::runtime_error("Illegal state exception!")
Error in <TRint::HandleTermInput()>: std::runtime_error caught: Illegal state exception!

>> throw std::invalid_argument("Error: number of elements cannot be negative.")
Error in <TRint::HandleTermInput()>: std::invalid_argument caught: Error: number of elements cannot be negative.
>> 
>> throw std::out_of_range("Invalid element index. Aborting operation!")
Error in <TRint::HandleTermInput()>: std::out_of_range caught: Invalid element index. Aborting operation!

Catching exceptions:

void testException(std::function<void ()> action){
     try {
         action();
     }
     // handle of std::invalid_argument
     catch(const std::invalid_argument& ex){
          std::cerr << " [ERROR] {std::invalid_argument} \n => "
                    << ex.what() << "\n";
          return;
     }
     // handle of std::out_of_range
     catch(const std::out_of_range& ex){
          std::cerr << " [ERROR] {std::out_of_range} \n => "
                    << ex.what() << "\n";
          return;
     }
     catch(const std::exception& ex){
          std::cerr << " [ERROR] {std::exception} \n => "
                    << ex.what() << "\n";
          return;
     }
     std::cout << " [INFO] Executed gracefully. OK" << "\n";
}

Running:

>> testException([](){ throw std::runtime_error("Fatal error 0xBAF5F8");})
 [ERROR] {std::exception} 
 => Fatal error 0xBAF5F8

>> testException([](){ throw std::invalid_argument("Expected x = 10");})
 [ERROR] {std::invalid_argument} 
 => Expected x = 10
>> 

>> testException([](){ throw std::out_of_range("Error: given invalid index.");})
 [ERROR] {std::out_of_range} 
 => Error: given invalid index.
>> 

// Crash the program as it cannot handle this exception 
>> testException([](){ throw 10 ;})
Error in <TRint::HandleTermInput()>: Exception caught!

// Crash again!
>> testException([](){ throw "hello world" ;})
Error in <TRint::HandleTermInput()>: Exception caught!
>> 

>> testException([](){ throw std::overflow_error("Number cannot be represented. Overflow error!");})
 [ERROR] {std::exception} 
 => Number cannot be represented. Overflow error!
>> 

1.29.3 Types of Program termination

Types of program/process termination

C++ has two types of process termination, normal and abnormal.

  • normal termination (aka normal exit)
    • Happens when the main function finishes its execution returning an integer status code or the function std::exit(int status) is called. When it happens, all objects with automatic (stack allocated) and static storage (global objects) are destroyed and their destructors called.
  • abnormal termination (aka abnormal exits)
    • Happens when the function std::abort is called raising the SIGABRT signal (abort signal). This function terminates the program without calling destructors of automatic, thread or static storage duration, thefore resources are not cleaned up.
    • The function std::abort is also called when an assertion macro fails from <cassert> header fails.

Causes of Abnormal Termination - std::terminate

When the function std::terminate is called by the C++ runtime, it calls std::terminate_handler callback function which is set by default to std::abort, which abnormally termintes the program. The function called by std::terminate can be changed with std::set_terminate which sets a new terminate handler(aka callback).

The functions std::terminate and std::abort are called by the C++ runtime when:

  • Uncaught exception => A thrown exception is not caught with try … catch block.
  • Constructor of static (global object) and thread-local objects throw exceptions.
  • Noexcept specification is violated: a function or member function annotated with noexcept throws an exception.
  • A joinable std::thread (thread that is still running) is destroyed or assigned to.

Further Reading

1.29.4 Overriding std::terminate handler

This experiment program overrides std::terminate_handler for testing what happens with the application and destructors in normal and abnormal terminations.

Class DummayClass:

class DummyClass{
public:
    DummyClass()
    {
        std::puts(" [INFO] Object ctor => Constructor called.");
    }
    ~DummyClass()
    {
        std::puts(" [INFO] Object dtor => Destructor called.");
    }
};

Function main() part 1: Instantiante a dummy object of class DummyClass for testing destructor and constructor.

Function main() part 1:

DummyClass cls;

Function main() - part 2

  • Override std::terminate_handler via std::set_terminate if the environment variable named TERMINATE is set to the string "true".
const char* option = std::getenv("TERMINATE");
if(option != nullptr && std::string(option) == "true")
{

    std::puts(" [TRACE] Override std::terminate handler.");
    // Overrides the function called by std::terminate (std::abort by default).
    //
    // It accepts function pointer or a non-capturing lambda.
    // Override std::termiante setting std::terminate_handler callback
    std::set_terminate([](){
        std::cerr << " [FATAL] std::terminate() called; SIGABRT signal abort sent."
                  << std::endl;
        // terminate current process sending abrt
        std::abort();
    });
}

Function main() - part 3:

  • Register function to be called when the program is terminated via std::atexit. This function accepts function pointer or non-capturing lambda.
// Register function to be called when the program exits.
// Note: It does not override the normal termination behavior.
std::atexit([](){
    std::cerr  << " [INFO] Normal termination. Ok." << std::endl;
});

Function main() - part 4:

  • Handle command line arguments.
if(argc < 2) {
    std::puts(" [ERROR] Missing command. Shutdown");
    return 1;
}
std::string cmd = argv[1];

Function main() - part 5:

  • Simulate many types of normal and abornal terminations.
if(cmd == "terminate_normal1")
{
    std::puts(" [TRACE] Nothing.");
}
else if(cmd == "terminate_normal2")
{
    std::puts(" [TRACE] Call std::exit(int status_code);");
    std::exit(2);
}
else if (cmd == "terminate_except")
{
    std::puts(" [TRACE] Before throwing exception");
    throw std::runtime_error("Invalid input domain");
    std::puts(" [TRACE] After throwing exception");

} else if (cmd == "terminate_thread")
{
    using namespace std::chrono_literals;
    std::puts(" [TRACE] Joinable thread out of scope without calling .join() or detach");

    std::thread th{ [](){
            while(true) {
                std::puts(" [TRACE] Thread running ....");
                std::this_thread::sleep_for(1s);
            }
    }};

    // Missing thread::join() or thread::detach method call
    // => The runtime calls std::terminate()
}

std::cout  << " [TRACE] End of main function" << std::endl;

return 0;

Building:

$ clang++ test_terminate.cpp -o term -std=c++1z -g -Wall -Wextra -g -lpthread

Running:

  • Normal termination:

Termiante via main() return statement.

./term terminate_normal1
 [INFO] Object ctor => Constructor called.
 [TRACE] Nothing.
 [TRACE] End of main function
 [INFO] Object dtor => Destructor called.
 [INFO] Normal termination. Ok.

Terminate with std::exit.

./term terminate_normal2
 [INFO] Object ctor => Constructor called.
 [TRACE] Call std::exit(int status_code);
 [INFO] Normal termination. Ok.
  • Abornmal termination through uncaught exception

Run without overriding std::set_terminate

$ ./term terminate_except
 [INFO] Object ctor => Constructor called.
 [TRACE] Before throwing exception
terminate called after throwing an instance of 'std::runtime_error'
  what():  Invalid input domain
Aborted (core dumped)

Run overriding std::terminate_handler handler by setting environment variable TERMINATE to string 'true'.

$ env TERMINATE=true ./term terminate_except
 [INFO] Object ctor => Constructor called.
 [TRACE] Override std::terminate handler.
 [TRACE] Before throwing exception
 [FATAL] std::terminate() called; SIGABRT signal abort sent.
Aborted (core dumped)
  • Abornmal termination through joinable std::thread object out of scope without calling .join() or .detach().

Run without overriding std::terminate_handler

$ ./term terminate_thread
 [INFO] Object ctor => Constructor called.
 [TRACE] Joinable thread out of scope without calling .join() or detach
terminate called without an active exception
 [TRACE] Thread running ....
Aborted (core dumped)

Run without std::terminate_handler:

$ env TERMINATE=true ./term terminate_thread
 [INFO] Object ctor => Constructor called.
 [TRACE] Override std::terminate handler.
 [TRACE] Joinable thread out of scope without calling .join() or detach
 [FATAL] std::terminate() called; SIGABRT signal abort sent. [TRACE] Thread running ....

Aborted (core dumped)

1.29.5 Custom Exceptions

Example:

enum class ErrorFlags : unsigned {
    MemoryError     =  0x2A,
    NetWorkFailure  =  0xFA,
    DNSFailure      =  0x50, 
    UnknownError    =  0xAF
};

struct DownloadException: public std::exception {
    ErrorFlags errorCode;
    DownloadException(ErrorFlags errorCode)
       :errorCode{errorCode}{}  
    auto what() const throw() -> const char* {
         return "Error: Download failure. See error code. ";
    }
};

void testDownloadException(std::function<void ()> action){
    std::exception_ptr p;
    try {
        action();
    } catch(const DownloadException& ex){
        std::cerr << ex.what() << "\n";
        std::cerr << "[FAILURE] Error code = "
                  << std::hex <<  static_cast<unsigned>(ex.errorCode)
                  << std::dec
                  << "\n";
        return;
    } catch(...){
            // Catch all exceptions, log and rethrow        
        std::cerr << "[FAILURE] Unknown exception." << "\n";
        p = std::current_exception();
        std::rethrow_exception( p);     
    }
    std::cout << " [INFO] Download ended gracefully. OK." << "\n";
}

Testing:

>> testDownloadException([](){})
 [INFO] Download ended gracefully. OK.

>> testDownloadException([](){ throw DownloadException(ErrorFlags::NetWorkFailure); })
Error: Download failure. See error code. 
[FAILURE] Error code = fa
>> 
>> testDownloadException([](){ throw DownloadException(ErrorFlags::DNSFailure); })
Error: Download failure. See error code. 
[FAILURE] Error code = 50
>> 
>> testDownloadException([](){ throw DownloadException(ErrorFlags::UnknownError); })
Error: Download failure. See error code. 
[FAILURE] Error code = af
>> 

>> testDownloadException([](){ throw std::runtime_error("Fatal kernel failure!!"); })
[FAILURE] Unknown exception.
Error in <TRint::HandleTermInput()>: std::runtime_error caught: Fatal kernel failure!!

>> testDownloadException([](){ throw std::out_of_range("Invalid index"); })
[FAILURE] Unknown exception.
Error in <TRint::HandleTermInput()>: std::out_of_range caught: Invalid index
>> 

1.29.6 Example - code

File: exceptions.cpp

#include <iostream>
#include <string>
#include <iomanip>
#include <ostream>
#include <exception>

struct Error{
public:
    const char* reason;
    const int errorCode;    
    Error(int errorCode, const char* reason): errorCode(errorCode), reason(reason) {};
    void display(std::ostream& os){
        os << "Error code = " << errorCode << "; reason = " << reason << "\n";
    }
};

class NetworkFailure : public std::exception {
public:
    const char* what () const throw(){
        return " ==> Error: network failure.";
    }
} networkFailure;

struct OutOfmemoryError : std::exception {
    const char* what() const throw(){
        return "==> Error: there is no enough memory.";
    }
};

struct UnknownError {};

// Any object, class or type can be thrown like an Exception.
void exceptionThrower(int code){
    NetworkFailure networkFailure;
    switch(code){
    case 0:
        throw "Error (1) has happened";
        break;
    case 1:
        throw 4090;
        break;
    case 2:
        throw 'x';
        break;
    case 3:
        throw Error(0xffa, "Failure to download update.");
        break;
    case 4:
        throw Error(0xff5, "Invalide input parameters.");
        break;
    case 5:
        throw std::runtime_error("Runtime error happened.");
        break;
    case 6:
        throw std::bad_alloc();
        break;
    case 7:
        throw OutOfmemoryError();
        break;
    case 8:
        throw networkFailure;
        break;  
    default:
        throw UnknownError();
    }
}

void handleException(int n){
    try {
        exceptionThrower(n);
    } catch (const char* perror){
        std::cerr << "==> [String] An error of type string happened: " << perror << std::endl;
    } catch (int errorCode){
        std::cerr << "==> [Int   ] Returned error code = " << errorCode << std::endl;       
    } catch (char x){
        std::cerr << "==> [Char  ] Returned char error code = " << x << std::endl;      
    } catch (Error err){
        err.display(std::cerr);
    } catch (const std::exception& ex){
        std::cerr << "Catch an std::exception. = " << ex.what() << std::endl;
    } // Catch all exceptions - Not recommeded. 
    catch (...) {
        std::cerr << "==> Unknown exception." << std::endl;
    }
}

int main(){
    handleException(0);
    handleException(1);
    handleException(2);
    handleException(3);
    handleException(4);
    handleException(5);
    handleException(6);
    handleException(7);
    handleException(8);
    handleException(100);
    std::cout << "Finish successfully" << std::endl;    
    return 0;
}

Compiling and running:

$ clang++ -std=c++11 exceptions.cpp -o out.bin && ./out.bin

==> [String] An error of type string happened: Error (1) has happened
==> [Int   ] Returned error code = 4090
==> [Char  ] Returned char error code = x
Error code = 4090; reason = Failure to download update.
Error code = 4085; reason = Invalide input parameters.
Catch an std::exception. = Runtime error happened.
Catch an std::exception. = std::bad_alloc
Catch an std::exception. = ==> Error: there is no enough memory.
Catch an std::exception. =  ==> Error: network failure.
==> Unknown exception.
Finish successfully

1.29.7 Exception Safety Guarantees

Exception-Safety Concept:

  • According to Stroustrup, an operation is said to be exception-safe it it leaves the object in a valid and well-defined state after its termination when an exception is thrown.
  • An Exception-Safe code should:
    • Left the the program in a valid state after an exception happens.
    • avoid memory leaks => always release the allocated memory when an exception happens or no longer needed.
    • avoid resource leaks => always release acquired resources such as socket handlers, file descriptors, database connection handlers, locks and so on when an exception happens or no longer needed.
    • not allow data structures being corrupted.

Levels of Excpetion-Safety Guarantees

Exception-safety guarantees are a set of promises made by class or library to a client code allowing better reasoning about exceptions. A library or code can provide the following exception-safety guarantees, aka levels of exception-safety guarantees:

  • basic exception-safety guarantees (aka no-leak guarantee)
    • No resources are leaked and the invariants of the component are preserved.
    • The component must be in a valid state after the exception is thrown, but it does not mean that its state was kept.
    • This is the minimum standard that a library, component, container or class should implement.
  • strong exception-safety guarantees (aka commit or rollback semantics)
    • Some functions from standard library with strong exception safety guarantee:
      • Member functions for several containers: push_back, push_front and rehash.
    • The component state is kept the same as it was before the operation has been completed successfuly or thrown an exception.
    • Summary: if an operation fails, it has no effects.
  • no-throw guarantees (aka no-fail or failure transparency)
    • The operation does not throw an exception and all operations are guaranteed to complete successfully with any exception handled internally, therefore not affecting any client code.
    • Some functions from standard library with no-throw exception-safe guarantee.
      • Member functions of several containers: erase, pop_back, pop_front and clear.
      • Function: std::swap
    • Functions with no-throw guarantee can be explicitly annotated with noexcept specifier.
    • The noexcepet specifier annotation for copy constructor and move constructor allows many functions from the C++ standard library to choose the most efficient function overloading based on this annotation. For instance, if the move constructor of a given type M is not marked with noexcept specfier, the member function push_back of std::vector container will not use this constructor, instead it will use the copy constructor incurring on a copy overhead and less efficiency. If the annotation is used, the push_back member function will use move-constructor which makes the operation more efficient.
    • Note: If a function or member function is annotated with noexcept and throws any exception, the function std::terminate is called terminating the process immediately.
  • No exception safety
    • Non guarantees are provided and the object or container may be in a corrupt state. No exception-safety should be avoided.
  • Exception neutral (additional exception-safety level)
    • An exception neutral code does not throw or catch exception. It only forwards exceptions thrown in the code that it calls to the client code.

Principles for exception-safe code

  • Avoid resouce leaks and memory leaks.
  • Always keep an object in valid state when an exception is thrown.
  • Do not destroy a piece of data before its replacement can be store.

Language features for exception safety

  • try-catch block
  • RAII Idiom - Resource Acquisition Is Initialization technique.
    • Resources, which can be heap-allocated memory, socket handlers, file descriptiors, database handlers and so on, are acquired in constructor and released at destructor which are determinstic and always called when an exception happens.
    • The C++ language guarantees that a destructor always will be called at the end of scope where the object is defined or when an exception happens.
    • Note: A destructor should always catch all exceptions and never allow them to propagate from them, otherwise, the program will be terminated immediately.
  • Copy-and-swap idiom

References:

1.30 C++11 Tuples

1.30.1 Overview

Tuples are a generic containers (product type) common in functional languages for storing heterogeneous types. Some use cases for tuples are, grouping unrelated types into a single object and returning multiple values from functions. C++11 provide tuples in the standard library in the header <tuple>.

1.30.2 Function std::make_tuple

Example:

>> auto coord0 = std::make_tuple("Madrid", 40.4168, -3.7038);

>> std::get<0>(coord0)
(const char*) "Madrid"
>> std::get<1>(coord0)
(double) 40.416800
>> std::get<2>(coord0)
(double) -3.7038000
>> 

>> coord0
(std::tuple<const char *, double, double> &) 
{ "Madrid", 40.416800, -3.7038000 }

>> std::cout << "City = " << std::get<0>(coord0) << "\n";
City = Madrid

>> std::cout << "Latitude = " << std::get<1>(coord0) << "\n";
Latitude = 40.4168

>> std::cout << "Longitude = " << std::get<2>(coord0) << "\n";
Longitude = -3.7038

1.30.3 Example in Cling REPL

Create database row:

using DbRow = std::tuple<int, double, std::string>;

Create an instance of the tuple:

>> DbRow(200, 500.23, "Diesel Power Generator")
(DbRow) { 200, 500.23000, "Diesel Power Generator" }

>> DbRow(51300, 1.2, "1 kg Orange")
(DbRow) { 51300, 1.2000000, "1 kg Orange" }
>> 

Get tuple elements: (std::get<INDEX>)

>> auto row = DbRow(51300, 1.2, "1 kg Orange")
{ 51300, 1.2000000, "1 kg Orange" }

>> std::get<0>(row)
(int) 51300

>> std::get<1>(row)
(double) 1.2000000

>> std::get<2>(row)
"1 kg Orange"
>> 

>> std::cout << std::get<0>(row) << " / " << std::get<1>(row) << " / " << std::get<2>(row) << "\n\n" ;
51300 / 1.2 / 1 kg Orange

Split tuple elements: (std::tie)

>> std::tie(Index, Price, Name) = row
(std::tuple &) { 51300, 1.2000000, "1 kg Orange" }

>> Index
(int) 51300

>> Price
(double) 1.2000000

>> Name
(std::string &) "1 kg Orange"
>> 

Create function to print tuple:

void printTuple(const DbRow& row){
        std::cout << "Id    = " << std::get<0>(row) << "\n";
        std::cout << "Name  = " << std::get<1>(row) << "\n";
        std::cout << "Price = " << std::get<2>(row) << "\n";
}

Pass tuple to function:

>> printTuple(row)
Id    = 51300
Name  = 1.2
Price = 1 kg Orange

>> printTuple(std::tuple<int, double, std::string>(100, 12.5, "1kg Fresh Bacon"))
Id    = 100
Name  = 12.5
Price = 1kg Fresh Bacon
>> 

>> printTuple(DbRow{100, 12.5, "1kg Fresh Bacon"})
Id    = 100
Name  = 12.5
Price = 1kg Fresh Bacon

>> printTuple({100, 12.5, "1kg Fresh Bacon"})
Id    = 100
Name  = 12.5
Price = 1kg Fresh Bacon
>>                                                 

Return tuple from function:

// Profit margin in percent 
DbRow makeProduct(double profitMargin, int index, double price, const std::string& name){
        double factor = (profitMargin + 100.0) / 100.0; 
        return {index, factor * price, name };
}

>> auto p = makeProduct(15.0, 600, 1.5, "Water bottle 1L - Brand XPMFN")
{ 600, 1.7250000, "Water bottle 1L - Brand XPMFN" }
>> 

>> std::get<0>(p)
(int) 600

>> std::get<1>(p)
(double) 1.7250000

>> std::get<2>(p)
"Water bottle 1L - Brand XPMFN"

Change tuple elements:

>> auto r = DbRow{-1, 0, "Dummy Product"}
{ -1, 0.0000000, "Dummy Product" }

>> std::get<0>(r) 
(int) -1

>> std::get<0>(r) = 500
(int) 500

>> std::get<1>(r)
(double) 0.0000000

>> std::get<1>(r) = 200.0 
(double) 200.00000

>> std::get<2>(r)
"Dummy Product"

>> std::get<2>(r) = "Super high power diesel"
"Super high power diesel"

>> r
{ 500, 200.00000, "Super high power diesel" }

1.30.4 Example - sample source code

Example:

  • File: cpp11Tuples.cpp
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <iomanip>
#include <tuple>

// Type synonym 
using DbRow = std::tuple<int, double, std::string>;

std::string toString(const DbRow& row){
        std::stringstream ss;
        ss << "id = "       << std::get<0>(row)
           << " ; price = " << std::get<1>(row)
           << "; name = "   << std::get<2>(row)
           << std::endl;
        return ss.str();
}

void printInventory(const std::vector<DbRow>& inventory){
        using std::cout;
        using std::endl;
        for(auto &p: inventory){
                int id;
                double price;
                std::string name;
                std::tie(id, price, name) = p;
                cout << std::right << std::setw(10) << id
                         << std::right << std::setw(10) << std::setprecision(2) << std::fixed << price
                         << std::right << std::setw(5) << " "
                         << std::left  << std::setw(10) << name
                         << "\n";
        }
}

int main(){
        using std::cout;
        using std::endl;
        using std::string;

        // Database row 
        std::tuple<int, double, string> row1(412, 200.41, "battery pack");
        DbRow row2(100, 20.5, "something else");

        cout << "Product 1 = " << toString(row1) << endl;
        cout << "Product 2 = " << toString(row2) << endl;

        std::vector<DbRow> dataset = {
                DbRow(100, 2.5,  "apples"),
                DbRow(200, 1.4,  "oranges"),
                DbRow(300, 12.4, "sugar"),
                DbRow(205, 20.5, "coffee")
        };

        printInventory(dataset);         
        return 0;
}

Running:

$ g++ cpp11Tuples.cpp -o cpp11Tuples.bin -g -std=c++11 -Wall -Wextra && ./cpp11Tuples.bin
Product 1 = id = 412 ; price = 200.41; name = battery pack

Product 2 = id = 100 ; price = 20.5; name = something else

       100      2.50     apples    
       200      1.40     oranges   
       300     12.40     sugar     
       205     20.50     coffee    

1.31 Handling Signals

1.31.1 Oveview

Signals are software interrupts sent to a process by the operating system. The signals can be generated by memory corruption; user pressing Ctrl + Z in ther terminal; killing the process on Unix-like OSes with command $ kill <Process-ID> and so on.

Headers:

More about signals at:

Table 13: Listing of signals that can be sent to a process
Signal Macro Name Description
SIGINT Signal Interrupt Generated when user types Ctrl + C
SIGTSTP (Posix) Terminal Stop Signal Generated when user types Ctrl + Z on any Unix-like OS.
SIGTERM Signal Terminal Request Signal sent by Unix command $ kill <PID>
SIGABRT Signal Abort  
SIGFPE Singla Float-Point Exception  
SIGILL Signal Illegal Instruction  
SIGSEGV Signal Segmentation Violation  
SIGQUIT (Posix) Signal Quit Terminate program with a core dump (Ctrl + \) Unix-like OS only.
SIGHUP (Posix) Signal hang-up Parent terminal process killed.

Function Signal:

Install signal handler or callback function that is called when a given signal is sent to the current process.

Signature:

 void signal(int SIGNAL, void (*HANDLER) (int) );

 // Or: 
 void signal( int SIGNAL,            /* Signal */
              void (*HANDLER) (int)  /* Callback (function-pointer) */
            );

// Or: 
 void signal( int SIGNAL,            /* Signal */
              void HANDLER (int)  /* Callback (function-pointer) */
            );

1.31.2 Example

Headers:

#include <iostream>
#include <map>
#include <string>
#include <functional> 
#include <csignal>
#include <fstream> 

// Unix specific (not valid for MS-Windows)
#include <unistd.h> // Import getpid()

Class Dummy used for creating the global static object dummyGlobal:

class Dummy{
public:
    Dummy(){
      std::cerr << " [TRACE] Program initialized OK "  << "\n";
   }
   ~Dummy(){
      std::cerr << " [TRACE] All objects destroyed. Program End OK. "  << "\n";
   }
};

Dummy dummyGlobal;

Function: signal_handler

void signal_handler(int signal)
{
   // static auto logger = []{
   //   auto log = std::ofstream("signals.log");
   //   log << " [INFO] Initialized Logger OK." << std::endl;
   //   return log; 
   // }();

   auto& logger = std::cerr;

   const static std::map<int, std::string> signalDatabse = {
        {SIGINT,   "SIGINT"}
       ,{SIGABRT,  "SIGABRT"}
       ,{SIGSEGV,  "SIGSEV"}
       ,{SIGFPE,   "SIGFPE"}
       ,{SIGTSTP,  "SIGTSTP"}
       ,{SIGTERM,  "SIGTERM"}
       ,{SIGQUIT,  "SIGQUIT"}
     };

   // std::cin.ignore(1000);
   std::cin.clear();

   std::string sigName;

   auto it = signalDatabse.find(signal);
   if(it != signalDatabse.end())
     sigName = it->second;
   else
     sigName = "UNKNOWN";

   logger << "\n [INFO] " << __FUNCTION__ << "() "
          << " Received signal = {" << sigName << "} => code = " << signal << std::endl;
}

Function main():

pid_t pid = ::getpid();
std::cout << " Process ID =  " << pid << "\n";
std::cout << " Attach to it with $ gdb --tui --pid=" << pid << "\n";

std::signal(SIGINT,  &signal_handler);
std::signal(SIGFPE,  &signal_handler);
std::signal(SIGTERM,  signal_handler);
std::signal(SIGTSTP,  signal_handler);
std::signal(SIGQUIT,  signal_handler);

int n = 0;
char ch = 0;

std::string line;

while(std::cin.good()){
   std::cout << " Variable n = " << n++ << "\n";
   std::cout << " => Input = ";
   std::getline(std::cin, line);

   // Simulate float-point exception 
   if(line == "sigfpe"){
      std::cout << " 10.0 / 0.0 = " << 10.0 / 0.0 << "\n";
   }
}

return EXIT_SUCCESS;

Compiling:

$ clang++ handle-signal.cpp -o handle-signal.bin -std=c++1z -g -O0 -Wall 

Running:

$ ./handle-signal.bin 
 [TRACE] Program initialized OK 
 Process ID =  27650
 Attach to it with $ gdb --tui --pid=27650
 Variable n = 0
 => Input = 
 Variable n = 1
 => Input = 
 Variable n = 2

 # User types Ctrl + C
 => Input = ^C
 [INFO] signal_handler()  Received signal = {SIGINT} => code = 2

 Variable n = 3

 # User types Ctrl + Z 
 => Input = ^Z
 [INFO] signal_handler()  Received signal = {SIGTSTP} => code = 20

 # User types Ctrl + \ 
 Variable n = 4
 => Input = ^\
 [INFO] signal_handler()  Received signal = {SIGQUIT} => code = 3

 # User runs from another terminal $ kill 27650
 Variable n = 5
 => Input = 
 [INFO] signal_handler()  Received signal = {SIGTERM} => code = 15
 [TRACE] All objects destroyed. Program End OK. 
(base) 

1.32 Pointer to Function or Function Pointer

1.32.1 Overview

In C++, functions pointers are not so common as in C. However, they are still useful when it is necessary to call a C-library which uses this type of pointer for callbacks and event handlers.

It is preferrable to use std::function for callbacks and higher order functions instead of function pointers as std::function can be used with anything callable, such as function pointers, functors (function-objects) or C++11 lambdas.

Use cases of function pointers in C:

  • Callbacks
  • Higher Order Functions
  • Event Handlers

Use cases of function pointers in C++:

  • Type erasure of functions.
  • Interfacing C-libraries.
  • Creating C++ wrappers for C-libraries.
  • Load functions at runtime from a DLL, shared library on Windows or Shared Object on U*nix (Linux, BSD, OSX, …).

Function pointer variable declaration:

ReturnType (* VariableName) (ArgType0, ArgType1, ArgType2 ....);

// OR -> Intitalized to null 
ReturnType (* VariableName) (ArgType0, ArgType1, ArgType2 ....) = nullptr;

// Initialized to the address of some function 
ReturnType (* VariableName) (ArgType0, ArgType1, ArgType2 ....) = FUNCTION1;
ReturnType (* VariableName) (ArgType0, ArgType1, ArgType2 ....) = &FUNCTION1;

Function pointer Type Synonym - Prior to C++11

typedef ReturnType (* PointerAliasType) (ArgType0, ArgType1, ArgType2 ....);
PointerAliasType fp = nullptr;
PointerAliasType fp = &FUNCTION1;
PointerAliasType fp = FUNCTION1;

// Call 
ReturnType result = fp(arg0, arg1, arg2, ... argn1);

Function pointer Type Synonym - C++11 (better and more readable)

using PointerAliasType = ReturnType (*) (ArgType0, ArgType1, ArgType2 ....);
// Or: 
using PointerAliasType = auto (ArgType0, ArgType1, ArgType2 ....) -> ReturnType;

Examples:

  • void (* fpointer)(int);
    • Function pointer taking an integer as paremeter and returns nothing, void.
  • void* (* fp)(double *);
    • Function pointer that points to a function taking a double pointer (double *) and returns a void pointer.
  • int (* myFunPointer)(int, int);
    • Function pointer that points to a function that takes two integers as parameters and returns an integer.
  • double (* FunPointer) (double) = &sin;
    • Function pointer initialized with the address with of the function sin.
  • double (* FunPointer) (double) = sin;
    • Function pointer initialized with the address with of the function sin.
  • int (p2* funcp)(double, double) = NULL
    • Function pointer initialized to NULL or zero.
  • void (*p[10]) (void *) ()
    • Array of 10 pointers to a function that returns void

1.32.2 Function Pointer Declaration

// Declare a function pointer to a function 
// of type: double => double 
>> double (* mfunptr) (double) = nullptr;

>> mfunptr = sin
(double (*)(double)) Function @0x7ff56aa56f80

>> mfunptr(M_PI)
(double) 1.2246468e-16

>> mfunptr(M_PI_2)
(double) 1.0000000
>> 

>> mfunptr(1)
(double) 0.0000000
>> mfunptr(100)
(double) 2.0000000
>> mfunptr(1000)
(double) 3.0000000
>> 

// Pass a non-capturing lambda 
// --------------------------------------------
>> mfunptr = [](double x){ return 3 * x; }
(double (*)(double)) Function @0x7ff56bb000c0

>> mfunptr(3)
(double) 9.0000000

>> mfunptr(5)
(double) 15.000000

// Pass a capturing lambda => Cannot pass capturing 
// lambdas to function pointers.
// --------------------------------------------

>> double k = 10.0
(double) 10.000000

//
>> mfunptr = [&k](double x){ return 3 * x + k;}
ROOT_prompt_31:1:13: error: 'k' cannot be captured because it does not have automatic storage duration
mfunptr = [&k](double x){ return 3 * x + k;}
            ^
>> mfunptr = [k](double x){ return 3 * x + k;}
ROOT_prompt_32:1:12: error: 'k' cannot be captured because it does not have automatic storage duration
mfunptr = [k](double x){ return 3 * x + k;}
           ^
ROOT_prompt_28:1:8: note: 'k' declared here
double k = 10.0

1.32.3 Function Pointer Callbacks

Example 1:

void doTimes(int n, void (* action)(int n)){
     for(int i = 0; i < n; i++)
             action(i);
}

void printLine(int i){
     std::cout << "i = " << i << " \n";
}

>> doTimes(4, printLine)
i = 0 
i = 1 
i = 2 
i = 3 

Example 2:

  • Higher order function for tabulating numerical functions implemented with function pointers.
#include <iostream>
#include <iomanip>

// Alternative 1: 
void tabulateFun1(double (* MathFunc) (double)){
     std::cout << std::setprecision(3) << std::fixed;
     for(int i = 0; i < 10; i++)
         std::cout << std::setw(5) << i
                   << std::setw(10) << MathFunc(i)
                   << "\n";
}

// Alternative 2: 
void tabulateFun2(double MathFunc (double)){
     std::cout << std::setprecision(3) << std::fixed;
     for(int i = 0; i < 10; i++)
         std::cout << std::setw(5) << i
                   << std::setw(10) << MathFunc(i)
                   << "\n";
}

// Syntax 3 (C++11)
void tabulateFun3(auto MathFunc (double) -> double){
     std::cout << std::setprecision(3) << std::fixed;
     for(int i = 0; i < 10; i++)
         std::cout << std::setw(5) << i
                   << std::setw(10) << MathFunc(i)
                   << "\n";
}

The syntax can be simplified with type synonym using "using" (C++11) or typedef (old standard).

// Type Synonym [1] (C++11)
using MathFunPtr = double (*) (double);
// Type Synonym [2] (C++11)
using MathFunPtr = auto (double) -> double;

// Type Synonym [3] (Old standards)
typedef double (* MathFunPtr) (double);

void tabulateFun4(MathFunPtr MathFunc){
     std::cout << std::setprecision(3) << std::fixed;
     for(int i = 0; i < 10; i++)
         std::cout << std::setw(5) << i
                   << std::setw(10) << MathFunc(i)
                   << "\n";
}

It is not recomended to implement this function using function pointers. It is better to use the type std::function available in the header <functional> as it can also work with anything callable such as C++11 lambdas, functions-objects and function pointers.

  • Better implementation, more C++ friendly using C++11 std::function from header <functional>.
void tabulateFunCPP11(std::function<double (double)> MathFunc){
     std::cout << std::setprecision(3) << std::fixed;
     for(int i = 0; i < 10; i++)
         std::cout << std::setw(5) << i
                   << std::setw(10) << MathFunc(i)
                   << "\n";
}

Running in CLING Repl:

>> tabulateFun1(sin)
    0     0.000
    1     0.841
    2     0.909
    3     0.141
   ... ... .... 

>> tabulateFun2(log)
    0      -inf
    1     0.000
    2     0.693
    3     1.099
    4     1.386
   ... ... .... 

>> tabulateFun3(&exp2)
    0     1.000
    1     2.000
    2     4.000
    3     8.000
  ... ... .... 

>> tabulateFun4(&cos)
    0     1.000
    1     0.540
    2    -0.416
   ... ... .... 

1.33 Pointer to Class Member Function

In addition to ordinary pointers and function pointers, C++ has member function pointers which can point to a particular class method and be used for performing indirect method calls.

Despite member function pointer are much less used than other types of pointers, they are still useful in lots of use cases such as:

  • Implementing callbacks => Example: QT Slots and Signals.
  • Creating warappers => Example: Boost.Python
  • Reflection

Pointer to member function declaration:

  • It declares a pointer-to-member function of the class CLASS_NAME. The pointer variable is named pVariable and has the type signature: (ARG0, ARG1, … ARGN) => RETURN_TYPE
// Define a pointer to member function named 'pVariable' 
RETURN_TYPE (CLASS_NAME::* pVariable) (ARG0, ARG1, ..., ARGN-1);

// Define a pointer to member function 'pVariable' set to null pointer. 
RETURN_TYPE (CLASS_NAME::* pVariable) (ARG0, ARG1, ..., ARGN-1) = nullptr;

// Define a pointer to member function named 'pVariable' initialized with the 
// address of method (member function) member_functionA.
// Note: The signature of member_functionA is: 
// RETURN_TYPE CLASS_NAME::member_functionA (ARG0, ARG1, ...)   
RETURN_TYPE (CLASS_NAME::* pVariable) (ARG0, ARG1, ..., ARGN-1) = &CLASS_NAME::member_functionA;

// Define a pointer to member function named 'pVariable' initialized with the 
// address of method (member function) memebr_funnctionA. 
RETURN_TYPE (CLASS_NAME::* pVariable) (ARG0, ARG1, ..., ARGN-1) = &CLASS_NAME::member_functionB;
  • Example: Pointer to any member function taking zero arguments and returning a string.
std::string (Dummy::* pMemfn) () = nullptr;
  • Invoking a pointer to member function. It is necessary an instance of the class in order to invoke the member function pointed by the pointer.
CLASS_TYPE obj;
pVariable = &CLASS_TYPE::member_function1;
(obj.*pVariable)(arg0, arg1, arg2, ... argn);
  • Type synonym with Typdef
typedef RETURN_TYPE (CLASS_NAME::* pMemberFunction) (ARG0, ARG1, ..., ARGN-1);
pMemberFunction pvar = &CLASS_NAME::member_functionA;
CLASS_NAME obj;
(obj.*pvar)(arg0, arg1, .... argn-1);
  • Type synonym with C++11 "using" keyword
using pMemberFunction = RETURN_TYPE (CLASS_NAME::*) (ARG0, ARG1, ..., ARGN-1);
pMemberFunction pvar = &CLASS_NAME::member_functionA;
CLASS_NAME obj;
(obj.*pvar)(arg0, arg1, .... argn-1);

Further Reading:

Example

File:

#include <iostream>
#include <string>
#include <iomanip>
#include <deque>
#include <map>
#include <cassert>

class Dummy {
private:
        std::string _name = "unnamed";
public:
        Dummy(){}
        Dummy(const std::string& name): _name(name){}
        ~Dummy() = default;
        std::string getName() {
                return "I am a dummy class named <" + _name + ">";
        }
        std::string getLocation() {
                return "Unknown location";
        }
        std::string operator()(){
                return "I am a function-object called: <" + _name + ">";
        }
        auto compute(double x, double y) -> double {
                return 4 * x + 5 * y;
        }   
};

class DummyB{
public:
        DummyB(){}
        ~DummyB() = default;
        std::string getName() {
                return "My name is DummyB";
        }
        std::string getLocation() {
                return "Location of dummyB location";
        }
        std::string operator()(){
                return "I am the class DummyB";
        }
};

// Create type synonym to any member function of Dummy class
// which takes no parameter and returns a string.
typedef std::string (Dummy::* pDummyMemFnStr)();

void invokeMemberFun(Dummy& obj, pDummyMemFnStr pMemfn){
        std::cout << " [1] Method invocation returned value: " << (obj.*pMemfn)() << "\n";
}

// Create type synonum with the new "using" C++11 syntax
using pDummyMemFnStrCPP11 = std::string (Dummy::*)();

auto invokeMemberFun2(Dummy& obj, pDummyMemFnStrCPP11 pMemfn) -> void {
        std::cout << " [2] Method invocation returned value: " << (obj.*pMemfn)() << "\n";
}

template<class T>
auto invokeMemberFun3(T& obj, std::string (T::* pMemfn)()) -> void{
        std::cout << " [3] Method invocation returned value: " << (obj.*pMemfn)() << "\n";
}

template<class T, class R, class ... Args>
auto invokeMemfn(R (T::* pMemfn) (Args ... args), T& obj, Args ... arglist) -> R{
        return (obj.*pMemfn)(arglist ...);
}

int main(){
        const auto nl = std::string("\n");
        const auto nl2 = std::string("\n\n");
        const std::string line = "--------------------------------------------------\n";

        std::cout << nl << "=== Experiment 1 ===============" << nl2;
        std::cout << line;
        // Pointer to member function to any member functions
        // (aka method) which takes no argument and returns a string
        // of signature: () => std::string
        //.....................................................
        std::string (Dummy::* pMemfn) () = nullptr;
        // std::string (Dummy::* pMemfn) ();
        if(pMemfn == nullptr)
                std::cerr << " [INFO] Pointer not initilialized yet." << nl;

        Dummy d("DUMMY");

        // Set the function pointer to member function getName().
        pMemfn = &Dummy::getName;
        // Invoke pointer to member function (aka pointer to method)
        std::cout << "Name     = " << (d.*pMemfn)() << nl2;
        assert((d.*pMemfn)() == "I am a dummy class named <DUMMY>");

        if(pMemfn != nullptr)
                std::cerr << " [INFO] Pointer initilialized OK." << nl;

        // Set pointer to Dummy::getLocation
        pMemfn = &Dummy::getLocation;
        std::cout << "Location = " << (d.*pMemfn)() << nl;
        // assert((d.*pMemfn)() == "I am a dummy class named <DUMMY>"); 


        std::cout << nl << "=== Experiment 2 - Using typedef ===============" << nl;
        std::cout << line;
        pDummyMemFnStr pMemfn2 = nullptr;
        pMemfn2 = &Dummy::getName;
        std::cout << "d.getName() == " << (d.*pMemfn2)() << nl;
        assert((d.*pMemfn2)() == "I am a dummy class named <DUMMY>");   

        std::cout << nl << "=== Experiment 3 - Invoking member function with free function =" << nl;
        std::cout << line;
        // execute d.getName() 
        invokeMemberFun(d, &Dummy::getName);
        // execute d.getLocation() 
        invokeMemberFun(d, &Dummy::getLocation);
        // execute d() 
        invokeMemberFun(d, &Dummy::operator());

        std::cout << nl << "=== Experiment 4 - Invoking member function with free function C++11" << nl;
        std::cout << line;
        invokeMemberFun2(d, &Dummy::getName);
        invokeMemberFun2(d, &Dummy::getLocation);
        invokeMemberFun2(d, &Dummy::operator());

        std::cout << nl << "=== Experiment 5 - Pointer to member functions in STL deque collection" << nl;
        auto plist = std::deque<pDummyMemFnStrCPP11>();
        plist.push_back(&Dummy::getName);
        plist.push_back(&Dummy::getLocation);
        plist.push_back(&Dummy::operator());
        for(const auto& p: plist)
                std::cout << " (+) Calll returned = " << (d.*p)() << nl;

        std::cout << nl << "=== Experiment 6 - Pointer to member functions in map collection" << nl;
        auto dict = std::map<std::string, pDummyMemFnStrCPP11>();
        dict["getName"]     = &Dummy::getName;
        dict["getLocation"] = &Dummy::getLocation;
        dict["callme"]      = &Dummy::operator();
        for(const auto& kv: dict)
                std::cout << std::right << std::setw(20)  << "invoke(object, "
                          << std::setw(15) << kv.first << ")"
                          << " = " << (d.*(kv.second))() << nl;

        std::cout << nl << "=== Experiment 7 - Template " << nl;
        DummyB b;
        invokeMemberFun3(d, &Dummy::getName);
        invokeMemberFun3(b, &DummyB::getName);
        invokeMemberFun3(d, &Dummy::getLocation);
        invokeMemberFun3(b, &DummyB::getLocation);

        std::cout << nl << "=== Experiment 8 - Template " << nl;
        std::cout << line;
        std::cout << "d.getName()         = " << invokeMemfn(&Dummy::getName, d) << nl;
        std::cout << "d.compute(3.0, 4.0) = " << invokeMemfn(&Dummy::compute, d, 3.0, 4.0) << nl;

        return 0;
}

Running:

$ clang++ member-function-pointer.cpp -o member-function-pointer.bin -std=c++1z -Wall -Wextra  
$ ./member-function-pointer.bin

=== Experiment 1 ===============

--------------------------------------------------
 [INFO] Pointer not initilialized yet.
Name     = I am a dummy class named <DUMMY>

 [INFO] Pointer initilialized OK.
Location = Unknown location

=== Experiment 2 - Using typedef ===============
--------------------------------------------------
d.getName() == I am a dummy class named <DUMMY>

=== Experiment 3 - Invoking member function with free function =
--------------------------------------------------
 [1] Method invocation returned value: I am a dummy class named <DUMMY>
 [1] Method invocation returned value: Unknown location
 [1] Method invocation returned value: I am a function-object called: <DUMMY>

=== Experiment 4 - Invoking member function with free function C++11
--------------------------------------------------
 [2] Method invocation returned value: I am a dummy class named <DUMMY>
 [2] Method invocation returned value: Unknown location
 [2] Method invocation returned value: I am a function-object called: <DUMMY>

=== Experiment 5 - Pointer to member functions in STL deque collection
 (+) Calll returned = I am a dummy class named <DUMMY>
 (+) Calll returned = Unknown location
 (+) Calll returned = I am a function-object called: <DUMMY>

=== Experiment 6 - Pointer to member functions in map collection
     invoke(object,          callme) = I am a function-object called: <DUMMY>
     invoke(object,     getLocation) = Unknown location
     invoke(object,         getName) = I am a dummy class named <DUMMY>

=== Experiment 7 - Template 
 [3] Method invocation returned value: I am a dummy class named <DUMMY>
 [3] Method invocation returned value: My name is DummyB
 [3] Method invocation returned value: Unknown location
 [3] Method invocation returned value: Location of dummyB location

=== Experiment 8 - Template 
--------------------------------------------------
d.getName()         = I am a dummy class named <DUMMY>
d.compute(3.0, 4.0) = 32

1.34 Pointer to Class Member Variable

1.34.1 Summary

Pointers to members variables provides an way to access class member data indirectly.

Among other things, it has the following use cases:

  • Create setters/getters
  • Serialization
  • Reflection
  • Indirect Access
  • Security research.
  • Get the member data pointer offset in bytes.

Syntax for pointer to member data

  • Declare a pointer to member of a certain class of an specific type.
MEMBER_TYPE  CLASS_NAME::* PointerVariableName;
  • Declare a pointer to member intialized to null.
MEMBER_TYPE  CLASS_NAME::* PointerVariableName = nulltpr;
  • Declare a pointer to member intialized to the address of some member variable.
MEMBER_TYPE  CLASS_NAME::* PointerVariableName = &CLASS_NAME::memberVariable;

Syntax for type alias/synonym of pointer to member data

  • Before C++11 - type alias with the typedef keyword.
typedef MEMBER_TYPE CLASS_NAME::* TypeAliasName;
// Instance declaration: 
TypeAliasName pointerToMember;
TypeAliasName pointerToMember = nullptr;
TypeAliasName pointerToMember = &CLASS_NAME::MemeberVariable;
  • C++11 - type alias with 'using' keyword
using TypeAliasName = MEMBER_TYPE CLASS_NAME::*;
// Instance declaration: 
TypeAliasName pointerToMember;
TypeAliasName pointerToMember = nullptr;
TypeAliasName pointerToMember = &CLASS_NAME::MemeberVariable;

1.34.2 Example

// It could also be: struct Dummy{ ... }
class Dummy{
public:
        double      x;
        double      y;
        int         points;
        std::string name;
        double      z;
        char        ch;
        Dummy(){}
        Dummy(double x, double y, int points, const std::string& name):
                x(x), y(y), points(points), name(name){}
};

Creating a test object:

>>  auto obj = Dummy(20.2, 9.0, 10, "dummy");
>> obj.x
(double) 20.200000
>> obj.y
(double) 9.0000000
>> obj.points
(int) 10
>> obj.name
(std::string &) "dummy"
>> obj.z
(double) 0.0000000
>> obj.ch
(char) '0x00'

>> auto obj2 = Dummy(100.0, -10.0, 70, "objectx")
(Dummy &) @0x7fda2fd33060
>> 

Create an initialized to null pointer to double member data:

>> double Dummy::* p_to_member_double = nullptr;
>> 
>> p_to_member_double
(double Dummy::*) @0x43cbed0

Set pointer p_to_member_double to field x.

>> p_to_member_double = &Dummy::x
(double Dummy::*) @0x46bc260
>> 

// Acess field x of obj 
>> obj.* p_to_member_double
(double) 20.200000
>> 
>> obj.* p_to_member_double = -90.2
(double) -90.200000
>> obj.* p_to_member_double 
(double) -90.200000
>> obj.x
(double) -90.200000
>>  

>> obj2.* p_to_member_double
(double) 100.00000
>> 

>> obj2.* p_to_member_double = 200.0
(double) 200.00000
>> obj2.x
(double) 200.00000
>> 

Set pointer p_to_member_double to field y.

>> p_to_member_double = &Dummy::y;

>> obj.y
(double) 9.0000000
>> 
>> obj.* p_to_member_double
(double) 9.0000000
>> obj.* p_to_member_double = -190.0
(double) -190.00000
>> obj.* p_to_member_double
(double) -190.00000
>> obj.y
(double) -190.00000
>> 
>>

>> obj2.y
(double) -10.000000
>> obj2.* p_to_member_double
(double) -10.000000
>> 
>> obj2.* p_to_member_double = -900.223
(double) -900.22300
>> obj2.* p_to_member_double
(double) -900.22300

Create a type alias to pointer to member of type double.

// C++11 => Better!!
using PointerToMemberDouble = double Dummy::*;
// Old C++ standards => Cryptic  
typedef double Dummy::* PointerToMemberDoubleTypedef ; 

PointerToMemberDouble ptrdouble = &Dummy::z;

>> obj.* ptrdouble 
(double) 0.0000000

>> obj.z
(double) 0.0000000

>> obj.* ptrdouble 
(double) 0.0000000

>> obj.* ptrdouble = 100.0
(double) 100.00000

>> obj.* ptrdouble 
(double) 100.00000

>> obj.z
(double) 100.00000


>> obj2.z
(double) 0.0000000

>> obj2.z = 600.0
(double) 600.00000

>> obj2.* ptrdouble 
(double) 600.00000

>> obj2.* ptrdouble = -100.0
(double) -100.00000

>> obj2.* ptrdouble 
(double) -100.00000

>> obj2.z
(double) -100.00000
>> 
>> 

Use pointer to member with object pointer.

>> auto pobj1 = &obj
(Dummy *) @0x7ffd126ee978

>> pobj1->y
(double) -190.00000
>> 

>> PointerToMemberDouble ptom_double = &Dummy::y;
>> 
>> pobj1->* ptom_double
(double) -190.00000
>> 
>> pobj1->* ptom_double = -300.0;
>> 
>> pobj1->y
(double) -300.00000
>> 

Get member offset:

template<class Class, class Field>
size_t getOffset(Field Class::*fieldp) { 
        return size_t(&(static_cast<Class*>(nullptr)->*fieldp));
}

>> auto offset_x = getOffset(&Dummy::x)
(unsigned long) 0

>> auto offset_y = getOffset(&Dummy::y)
(unsigned long) 8

>> auto offset_z = getOffset(&Dummy::z)
(unsigned long) 56
>> 
>> auto offset_name = getOffset(&Dummy::name)
(unsigned long) 24
>> auto offset_points = getOffset(&Dummy::points)
(unsigned long) 16

Access class member x using x offset.

>> auto p = &obj
(Dummy *) @0x7ffd126ee978

>> reinterpret_cast<double*>(((size_t) p) + offset_x)
(double *) 0x7fda2fd33010

>> *reinterpret_cast<double*>(((size_t) p) + offset_x)
(double) -90.200000

>> *reinterpret_cast<double*>(((size_t) p) + offset_x) = -100.0
(double) -100.00000

>> p->x
(double) -100.00000
>> 
>> 
>> double& xref1 = *reinterpret_cast<double*>(((size_t) p) + offset_x)
(double) -100.00000
>> xref1 = 900.0
(double) 900.00000
>> p->x
(double) 900.00000
>> 


Access class member z using z offset.

>> p->z
(double) 100.00000
>> 

>> *reinterpret_cast<double*>(((size_t) p) + offset_z)
(double) 100.00000
>> 

>> *reinterpret_cast<double*>(((size_t) p) + offset_z) = -200.0
(double) -200.00000

>> p->z
(double) -200.00000
>> 

>> *reinterpret_cast<double*>(((size_t) &obj2) + offset_z)
(double) -100.00000

>> *reinterpret_cast<double*>(((size_t) &obj2) + offset_z) = 300.0
(double) 300.00000

>> *reinterpret_cast<double*>(((size_t) &obj2) + offset_z) = 300.0;

>> obj2.z
(double) 300.00000
>> 

Access field 'name' using its offset.

>> obj.name
(std::string &) "dummy"
>> 
>> *reinterpret_cast<std::string*>(((size_t) &obj) + offset_name)
(std::string &) "dummy"

>> *reinterpret_cast<std::string*>(((size_t) &obj) + offset_name) = "binary";
>> obj.name
(std::string &) "binary"
>> 

>> obj.name
(std::string &) "P0WN3D"

Generic solution for dealing with offsets:

template<typename FIELD, typename CLASS>
FIELD* getFieldPointer(const CLASS& obj, size_t offset ){
        return reinterpret_cast<FIELD*>(((size_t) &obj) + offset);
}

template<typename FIELD, typename CLASS>
FIELD getFieldValue(const CLASS& obj, size_t offset ){
        return *reinterpret_cast<FIELD*>(((size_t) &obj) + offset);
}

Get object's members values by their offset:

>> auto obj3 = Dummy(120.2, -89.0, 143, "blob");
>> obj3.x
(double) 120.20000
>> obj3.y
(double) -89.000000
>> obj3.z
(double) 0.0000000
>> obj3.z = 45.0;
>> obj3.name;
>> 

>> 
>> getOffset(&Dummy::x)
(unsigned long) 0
>> getOffset(&Dummy::y)
(unsigned long) 8
>> getOffset(&Dummy::z)
(unsigned long) 56
>> getOffset(&Dummy::name)
(unsigned long) 24
>> getOffset(&Dummy::points)
(unsigned long) 16
>> 

>> obj3.x
(double) 120.20000

>> getFieldValue<double>(obj3, 0)
(double) 120.20000
>> 

>> obj3.y
(double) -89.000000

>> getFieldValue<double>(obj3, 8)
(double) -89.000000
>> 

>> obj3.z
(double) 0.0000000
>> obj3.z = 45.0;

>> getFieldValue<double>(obj3, 56)
(double) 45.000000
>> 

>> obj3.name
(std::string &) "blob"
>> 
>> getFieldValue<std::string>(obj3, 24)
"blob"

Created: 2021-06-04 Fri 15:08

Validate