Skip to content

Latest commit

 

History

History
2790 lines (2063 loc) · 63.7 KB

cpp_cheatsheet.md

File metadata and controls

2790 lines (2063 loc) · 63.7 KB

INDEX

  1. Variable and fundamental data types

    1. Initializing a variable
    2. Intergers
    3. Floating point numbers
    4. Boolean Numbers
    5. Type casting
    6. Literal
    7. Constants
    8. Variable Scopes and duration
      1. Shadowing
      2. Global scope operator
  2. Console Input/Output

    1. Console I/O Methods
    2. Error Handling in Console Input
    3. A Program Handling All the Error Case in Input
  3. Generating Random Numbers

    1. Generating Random Numbers
    2. Function for generating Random Numbers Between A Range
  4. Advanced Data Types

    1. Array

      1. Array Indexes
      2. Representing Array Indexes with Enumerators
      3. Relation Between Array and Pointer
      4. Difference Between Array and Pointer to Array
      5. String Constants Using Fixed Sized Array and Pointer
    2. Pointers

      1. Definig NULL Pointer C++ Way
      2. Void Pointers
      3. Converting A Pointer Address to Integer
  5. Dynamic Memory Allocation

    1. Allocating Memory dynamically
    2. Deallocating memory
    3. Memory leak
  6. Reference Variables

  7. For Each Loop

  8. Functions

    1. Function Pointers
    2. Function Ellipsis
    3. Lambda Functios
  9. Object Oriented Programming

    1. Basic Class Example
    2. Constructors
    3. Destructors
    4. Static Member Variables
    5. Static Member Functions
    6. Member Type or Nested Type
    7. Access Specifiers
    8. Chaining member functions
    9. Difference between structs and classes in C++
    10. Friend function and friend class
  10. Operator Overloading

    1. Using Friend Function
    2. Using Normal Function
    3. Using Member Function
    4. Overloading IO operators
    5. Functors
    6. Overloading Typecast
    7. Copy Constructors
      1. Elision
      2. Implicit conversion, explicit and delete keyword
    8. Overloading assignment operator
    9. Shallow copy VS Deep copy
  11. Object Relationship

    1. Composition and Aggregation
      1. Composition
      2. Aggregation
    2. Association
    3. Dependencies
    4. Container Classes
    5. std::initializer_list
  12. Inheritance

    1. Order of construction
    2. Constructors and initialization of derived classes
    3. Order of Destruction
    4. Inheritance and Access Specifiers
    5. Overriding Behavior
      1. Redefining
      2. Expanding Existing Functionality
      3. Changing an Inherited Member's Access Specification
    6. Multiple Inheritance
  13. Virtual Functions

    1. Pointers to the Base Class of Derived Object
    2. Polymorphism
    3. override and final Specifiers
    4. Covariant Return Type
    5. Abstract Class, Pure Virtual Functions and Interface Class
    6. Virtual Base Class
    7. Object Slicing
      1. std::reference_wrapper
    8. Dynamic Casting
    9. Workaround for Friend Functions
  14. Templates

    1. Function Template
    2. Class Template
    3. Template Non-Type parameters
    4. Template Specialization

Variable and fundamental data types

Initializing a variable

  • Copy initialization:
int numRings = 20;
  • Direct initialization:
int numRings(20);
  • Uniform initialization:
int numRings{20}

If uniform initialization is used with no value. Default is 0.

int numRings{}

Integers

Fixed width integers

Will give fixed sized integer in all architecture.

#include <cstdint> // for fixed width integers

/* 
 * 8 bit singed integer. Many systems consider them as chars. So it is better
 * to not use them if using integers is the purpose
 */
int8_t var;

uint8_t var;	// 8 bit unsigned integer

intN_t var;	// N = 16, 32, 64 bits signed integer
uintN_t var;	// N = 16, 32, 64 bits unsigned integer

Fixed width integers performance is machine dependent. To get the fastest integer type on a specific machine use int_fastN_t. It will give the fastest integer which is at least N bits long. N = 8, 16, 32, 64.

int_fast32_t var;

To get the smallest integer which is at least N bits long use int_leastN_t where N = 8, 16, 32, 64.

int_least64_t var;

Floating point numbers

Setting precision of a floating point number:

#include <iostream>
#include <iomanip> // for std::setprecision()

int main(){
	double d{12.34567890};

	std::cout << "Without precision: " << d << std::endl;
	std::cout << std::setprecision(4);
	std::cout << "With precision: " << d << std::endl;

	return 0;
}

Special floating numbers: positive infinity, negative infinity, not a number.

#include <iostream>

int main(){
	float zero(0.0);
	float posinf = 5.0 / zero;
	float neginf = -5.0 / zero;
	float nan = zero / zero;

	std::cout << posinf << std::endl;
	std::cout << neginf << std::endl;
	std::cout << nan << std::endl;

	return 0;
}

Boolean Numbers

#include <iostream>

int main(){
	bool universeCameFromNothing(true);

	std::cout << "Universe came from nothing: " << universeCameFromNothing
		<< std::endl;

	std::cout << std::boolalpha;
	
	std::cout << "Universe came from nothing: " << universeCameFromNothing
		<< std::endl;

	return 0;
}

Type Casting

#include <iostream>

int main(){
	char ch(65);

	std::cout << ch << std::endl;
	std::cout << static_cast<int>(ch) << std::endl;

	return 0;
}

To get the variable or expression type.

#include <typeinfo>

int main(){
	int numerator(50);
	double denominator(5.0);

	std::cout << typeid(numerator).name() << std::endl;
	std::cout << typeid(numerator / denominator).name() << std::endl;
}

Literal

int integer(50);	// integer
float f(0.05f); 	// used f suffix as default literal is double type
double d(0.31416);	// double floating literal
int hex(0xa0);		// hexadecimal literal
int oct(012);		// Octal literal
int bin(0b1010);	// Binary literal

int longNumber(1'23'570) // c++14 only

Constants

const double avg(6.023e23);
const double massEarth; 	// This is not allowed

int x(50);
const int y(x)			// This is allowed

constexpr double adg(9.8);	// Compile time constant. Value of the constant
				// must be resolved in compile time otherwise
				// will generate an error. This is c++11 feature

Variable Scopes and duration

shadowing:

int number(5);

if(number == 5){
	int number; 	// local variable

	number = 10;	// will effect only local variable
			// this is shadowing

	std::cout << number << std::endl;	// will print local
}

std::cout << number << std::endl;	// will print outer block number

Global scope operator

:: is the global scope operator.

int x(50);	// global variable

int main(){
	int x(40);

	std::cout << x << std::endl;	// will print local x
	std::cout << ::x << std::endl;	// will print global x
}

Internal Variable: Can be used anywhere in the file they are defined but not out of the file.

External Variable: Cab be used across multiple files. Global variables are by default external. static keyword can be used to make them internal.

Console Input/Output

Console I/O Methods

  • std::cin is used for console input. std::cin takes inputs untill first whitespace.
#include <iostream>

int main(){
	int selection;

	std::cin >> selection;
	std::cout << "You have selected: " << selection << std::endl;

	return 0;
}
  • std::getline() is used to take whole line as input.
#include <iostream>
#include <string>

int main(){
	std::string name;

	std::cout << "Enter Name: ";
	std::getline(std::cin, name);
	std::cout << "Your name is: " << name;

	return 0;
}

Error Handling in Console Input

std::cin takes upto newline from the input stream. So it will be a problem if any other input function is used after taking numeric input from std::cin.

#include <iostream>
#include <string>

int main(){
	std::string name;
	int select;

	std::cout << "Select: ";
	std::cin >> select;

	std::cout << "Enter name: ";
	std::getline(std::cin, name);		// will not work
 
	std::cout << "Your name is: " << name << "! You have selected: "
		  << select;

	return 0;
}

To solve this problem std::cin.ignore(n, ch) can be used where n is the number of character to ignore from the input stream before ch character is found.

#include <iostream>
#include <string>

int main(){
	int select;
	std::string name;
	
	std::cout << "Select: ";
	std::cin >> select;

	std::cin.ignore(32767, '\n');

	std::cout << "Enter name: ";
	std::getline(std::cin, name);

	std::cout << "Hi " << name << "! You have selected " << select << std::endl;

	return 0;
}

A Program Handling All the Error Case in Input

Expand to see code
#include <iostream>

double getDouble(){
	double d;

	while(true){
		std::cout << "Enter: ";
		std::cin >> d;
		std::cin.ignore(32767, '\n');	/* clear '\n' from input stream */

		/*
		 * Input will fail if a valid number isn't typed. 
		 * if input fails, cin will set fail flag and stop extracting
		 * characters from input stream.
		 */
		if(std::cin.fail()){
			std::cout << "Please enter a floating number" << std::endl;
			std::cin.clear();				/* Clear fail flag */
			std::cin.ignore(32767, '\n');	/* Clear input stream */
		}
		else{
			return d;
		}
	}
}

char getOperator(){
	char op;

	while(true){
		std::cout << "Enter (+, -, * or /): ";
		std::cin >> op;
		std::cin.ignore(32767, '\n');
		
		if(op == '+' || op == '-' || op == '*' || op == '/'){
			return op;
		}
		else{
			std::cout << "Bad operator. Input again." << std::endl;
		}
	}
}

void printResult(double d1, char op, double d2){
	if(op == '+'){
		std::cout << d1 << " + " << d2 << " = " <<  d1 + d2 << std::endl;
	}
	else if(op == '-'){
		std::cout << d1 << " - " << d2 << " = " <<  d1 - d2 << std::endl;
	}
	else if(op == '*'){
		std::cout << d1 << " * " << d2 << " = " <<  d1 * d2 << std::endl;
	}
	else{
		if(d2 != 0){
			std::cout << d1 << " / " << d2 << " = " <<  d1 / d2 << std::endl;
		}
		else{
			std::cout << "Can't devide by zero!";
		}
	}
}

int main(){
	double d1 = getDouble();
	char op = getOperator();
	double d2 = getDouble();

	printResult(d1, op, d2);

	return 0;
}

Generating Random Numbers

Generating Random Numbers

#include <iostream>
#include <ctime>
#include <cstdlib>

int main(){
	/* For generating different seed each time the program runs */
	srand(static_cast<unsigned int>(time(0)));
	std::cout << rand();

	return 0;
}

Function for generating Random Numbers Between A Range

Using modulus:

#include <iostream>
#include <ctime>
#include <cstdlib>

int getRandom(int min, int max){
	return min + (rand() % (max - min + 1));
}

int main(){
	/* For generating different seed each time the program runs */
	srand(static_cast<unsigned int>(time(0)));
	std::cout << getRandom(1, 6);

	return 0;
}

But this method is biased to low numbers. Following method has less bias to low numbers.

#include <iostream>
#include <ctime>
#include <cstdlib>

int getRandom(int min, int max){
	static const double fraction = 1.0 / RAND_MAX;

	return min + ((max - min + 1) * (rand() * fraction));
}

int main(){
	/* For generating different seed each time the program runs */
	srand(static_cast<unsigned int>(time(0)));
	std::cout << getRandom(1, 6);

	return 0;
}

For details: http://www.learncpp.com/cpp-tutorial/59-random-number-generation/

Advanced Data Types

Array

Array Indexes

Array index must be a compile time constant.

#include <iostream>

int main(){
	int array[5]; // ok

	#define ARR_SIZE 5
	int array[ARR_SIZE];	// ok

	int const arr_size = 5;
	int array[arr_size];	// ok

	int arr_size = 5;
}

Representing Array Indexes with Enumerators

Array index can be represented with enumerators. In this way it makes arrays well documented:

#include <iostream>

namespace Store{
	enum Store{
		LM7805,
		MAX485,
		LM311,
		ATMEGA64,
		LED,
		MAX_ELEMENT
	};
}

int main(){
	int inStore[Store::MAX_ELEMENT];

	inStore[Store::LM7805] = 50;

	std::cout << "There are " << inStore[Store::LM7805] \
			  << " LM7805 in store" << std::endl;

	return 0;
}

Relation Between Array and Pointer

Arrays are actually pointers. It points to the first element of the array.

#include <iostream>

int main(){
	int arr[2] = {1, 2};

	/* Following two address will be same*/
	std::cout << arr << std::endl;
	std::cout << &arr[0] << std::endl;

	return 0;	
}

Difference Between Array and Pointer to Array

The type of the array is int (*)[n] if it is an integer array but the type of the pointer to that array is int *. Array type contains the size information of the array. When array is dereferenced or assigned to a pointer it implicitly converts itself into type * from type (*)[n] so size information is lost. Here is an example of this behaviour:

#include <iostream>

int main(){
	int arr[5] = {1, 2, 3, 4, 5};
	int *arrPtr = arr;	// arr is converted from int (*)[2] to int *

	/* Will print the size of the array which is 5 * 8 bytes */
	std::cout << sizeof(arr) << std::endl;

	/* Will print the size of the pointer which is 8 bytes */
	std::cout << sizeof(arrPtr) << std::endl;

	return 0;	
}

String Constants Using Fixed Sized Array and Pointer

#include <iostream>

int main(){
	/*
	 * keep the string constant in memory with r/w access
	 * and return the pointer to it.
	 * So string constant can be changed any time later
	 */
	char arr[] = "hello, world";

	/*
	 * Keep the string constant in read-only section of memory
	 * so it can't be changed
	 */
	char *text = "GNU is not Unix";

	/* As it is a constant so its better to initialize following way */
	const char *text = "GNU is not Unix";

	arr[0] = 'g';			// This OK

	/* 
	 * In this case as string constant is kept in read-only
	 * memory, doing this will generate segmentation
	 * fault
	 */
	*(text + 2) = 'O'; 

	std::cout << arr << std::endl;
	std::cout << text << std::endl;

	return 0;	
}

Pointers

Definig NULL Pointer C++ Way

From C++11 nullptr keyword can be used to define a NULL pointer.

#include <iostream>

int main(){
	int *ptr = nullptr;

	if(ptr){
		std::cout << "Not null" << std::endl;
	}
	else{
		std::cout << "Null" << std::endl;
	}
		
	return 0;	
}

Void Pointers

  • Can point to any data type
  • Have to cast manually to a data type before dereferencing.
  • Pointer arithmetic can't be done using void pointers as size of the obect isn't known
#include <iostream>

int main(){
	int x(5);
	void *ptr = &x;	// pointing to an integer

	std::cout << *static_cast<int*>(ptr) << std::endl;

	char ch = 'P';	// pointing to a char
	ptr = &ch;
	
	std::cout << static_cast<char*>(ptr) << std::endl;

	return 0;
}

Converting A Pointer Address to Integer

Using reinterpret_cast<>:

#include <iostream>

int main(){
	int x = 17;
	unsigned int addressX = reinterpret_cast<int>(&x);

	std::cout << addressX << std::endl;

	return 0;
}

Dynamic Memory Allocation

There are three basic type of memory allocation:

  • Static memory allocation: Static and global variables. Allocated when program runs. Persist throught the program life. Memory is taken from the stack.

  • Atomatic memory allocation: Function parameter and local variables. Allocated when enter into relevent block and deallocated when exited the block. Memory is taken from the stack.

  • Dynamic memory allocation: Memory allocated and deallocated dynamically. Memory is taken from the heap.

Allocating Memory dynamically

Dynamically memory is allocated using the new keyword.

#include <iostream>

int main(){
	int *ptr = new int;	// dynamically an integer is allocated.

	*ptr = 5;

	return 0;
}

Deallocating memory

Memory is deallocated using the delete keyword.

#include <iostream>

int main(){
	int *ptr = new int(5); // memory is allocated to the pointer

	/* memory is deallocated.
	 * memory is realesed by os.
	 */

	delete ptr; 

	/* still the pointer is holding the memory address
	 * so its better to make it null
	 */
	ptr = 0;
	ptr = nullptr;	// c++11

	return 0;
}

Memory leak

memory leak happens when allocated memory can't be deallocated.

#include <iostream>

void doSomthing(){

	/* Memory is allocated but not deallocated
	 * so memory leak happens when the variable
	 * goes out of scope as there is no way
	 * to deallocate in that case
	 */

	int *ptr = new int;
}

int main(){
	doSomthing();
	return 0;	
}
#include <iostream>

int main(){
	int *ptr = int new;

	int val;

	// assigning to a address with out deallocating
	ptr = &val;	// memory leak

	return 0;
}
#include <iostream>

int main(){
	int *ptr = new int(5);

	// allocating new memory without deallocating previous one
	int *ptr = new int(10);

	return 0;
}

Reference Variables

Create alias for a variable. Basically share same memory address. Must be initialized with an addressable object. Can be used in function to pass parameter by reference.

#include <iostream>

int main(){
	int x(10);
	int &y = x; // reference variable

	/*
	 * will output:
	 * 10
	 * 10
	 */
	std::cout << x << std::endl
	std::cout << y << std::endl

	return 0;
}

For Each Loop

  • Only works from c++11 above
  • Can't be used in case of decayed arrays and dynamically allocated arrays.
#include <iostream>

int main(){
	int fibseq[] = {1, 1, 2, 3, 5, 8, 13, 21};

	std::cout << "Fibonacci Sequence: ";
	for(const auto &elem: fibseq){
		std::cout << elem << " ";
	}
	std::cout << std::endl;

	return 0;
}

Functions

Function Pointers

#include <iostream>

int foo(int x){
	return x*x;
}

int main(){
	int (*square)(int) = foo;

	std::cout << square(10) << std::endl;

	return 0;
}
  • Can't be used for function's with default arguments

Function Ellipsis

Ellipsis can be used to pass variable length argument in a function.

#include <iostream>
#include <cstdarg>	// to use ellipsis

void printNum(int count, ...){
	va_list list;
	va_start(list, count);

	for(int arg = 0; arg < count; arg++){
		std::cout << va_arg(list, int) << std::endl;
	}

	va_end(list);
}

int main(){
	printNum(5, 1, 2, 3, 4, 5);

	return 0;
}

Lambda Functions

To create anonymous functions. Simple example:

#include <iostream>
#include <algorithm>
#include <vector>

int main(int argc, char *argv[]) {
    std::vector<bool> bitvect{1, 0, 0, 1};

    /* lambda function example: used to make ~bitvect */
    std::transform(bitvect.begin(), bitvect.end(), bitvect.begin(),
            [](bool b){ return b == 1 ? 0 : 1; });
    
    return 0;
}

Syntaxt:

[&](){}: Capture all outside variable by reference. [=](){}: Capture all outside variable by value. [&x](){}: Capture only outside variable x by reference. [x](){}: Capture only outside variable x by value. [&, x](){}: Capture all outside variable by reference but x by value. []() -> Type {}: To specify return type.

Object Oriented Programming

Basic Class Example

#include <iostream>

class Point{

	private:
		double m_x;
		double m_y;

	public:
		double getX(){
			return m_x;
		}

		void setX(double x){
			m_x = x;
		}

		double getY(){
			return m_y;
		}

		void setY(double y){
			m_y = y;
		}
};

int main(){
	Point p;
	
	p.setX(-2.5);
	p.setY(2.5);

	std::cout << "(" << p.getX() << ", " << p.getY() << ")" << std::endl;

	return 0;
}

Constructors

#include <iostream>

class Point{

	private:
		double m_x;
		double m_y;

	public:
		Point(double x = 0, double y = 0): m_x(x), m_y(y){
				// empty
		}

		double getX(){
			return m_x;
		}

		void setX(double x){
			m_x = x;
		}

		double getY(){
			return m_y;
		}

		void setY(double y){
			m_y = y;
		}

		void printPoint(){
			std::cout << "(" << m_x << ", " << m_y << ")" << std::endl;
		}
};

int main(){
	Point p(0.5, 0.5);
	
	p.printPoint();

	return 0;
}

In c++11 its possible to give default value in class memeber variable declaration.

#include <iostream>

class Point{

	private:
		double m_x = 0; // default value of x
		double m_y = 0;	// default value of y

	public:
		// member variable will be initialized with default value
		// if called
		Point(){
			//empty
		}

		double getX(){
			return m_x;
		}

		void setX(double x){
			m_x = x;
		}

		double getY(){
			return m_y;
		}

		void setY(double y){
			m_y = y;
		}

		void printPoint(){
			std::cout << "(" << m_x << ", " << m_y << ")" << std::endl;
		}
};

int main(){
	Point p;	// will call default constructor
	
	p.printPoint();

	return 0;
}

Destructors

#include <iostream>
#include <cassert>

class Point{

	private:
		double m_x;
		double m_y;

	public:
		Point(double x = 0, double y = 0): m_x(x), m_y(y){
			//empty
		}

		double getX(){
			return m_x;
		}

		void setX(double x){
			m_x = x;
		}

		double getY(){
			return m_y;
		}

		void setY(double y){
			m_y = y;
		}

		void printPoint(){
			std::cout << "(" << m_x << ", " << m_y << ")" << std::endl;
		}
};

class PointArray{

	private:
		Point *m_points;
		int m_length;

	public:
		PointArray(int length){
			m_points = new Point[length];
			m_length = length;
		}

		// Destructor
		~PointArray(){
			delete[] m_points;
		}

		void insert(Point &p, int index){
			assert(index >= 0 && index < m_length);

			m_points[index] = p;
		}

		Point& get(int index){
			assert(index >= 0 && index < m_length);

			return m_points[index];
		}
};

int main(){
	PointArray parr(5);
	
	Point p(2, 3);
	Point q;

	parr.insert(p, 0);
	
	q = parr.get(0);
	q.printPoint();

	return 0;
}

Static Member Variables

  • Will shared by all object.
  • Not bound to any object. Bound to the whole class. So it is possible to use this variable without any object.
#include <iostream>

class Static{
	public:
		static int id;
};

/* Have to initialize first otherwise linker error will generate */
int Static::id = 1;

int main(){
	Static s;
	Static t;

	/* Shared by both object */
	s.id = 5;
	std::cout << s.id << "\t" << t.id << std::endl;

	/* Bound to whole class */
	Static::id = 10;
	std::cout << s.id << "\t" << t.id << std::endl;

	return 0;
}

Static Member Functions

  • Not bound to any object.
#include <iostream>

class ID{
	private:
		static int m_id;

	public:
		static int getID(){
			return m_id;
		}
};

/* Have to initialize first otherwise linker error will generate */
int ID::m_id = 1;

int main(){
	std::cout << ID::getID << std::endl;

	return 0;
}

Member Types

In C++ classes can have memeber types or nested types. They make the class easy to maintain. For example in the following example it will be easy to change the type from int to double. It need to change in only one line.

#include <iostream>

class Point{
public:
    using point_t = int; // Member type

    Point(point_t x, point_t y): m_x(x), m_y(y){}

    void print(void){
        std::cout << "(" << m_x << ", " << m_y << ")";
    }

private:
    point_t m_x;
    point_t m_y;
};

int main(int argc, char *argv[]){
    Point p(10, 20);
    p.print();
    return 0;
}

Access Specifiers

public: private: protected:

Chaining member functions

#include <iostream>

class Point{
public:
    using point_t = int;

    Point(point_t x, point_t y): m_x(x), m_y(y){}

    Point& add(point_t x, point_t y){
        this->m_x += x; 
        this->m_y += y;
        return *this;
    }

    Point& mul(point_t x, point_t y){
        this->m_x *= x; 
        this->m_y *= y;
        return *this;
    }

    void print(void){
        std::cout << "(" << m_x << ", " << m_y << ")";
    }

private:
    point_t m_x;
    point_t m_y;
};

int main(int argc, char *argv[]){
    Point p(10, 20);
    p.add(3, 5).mul(7, 8).add(2, 3);
    p.print();
    return 0;
}

Difference between structs and classes in C++

C++ structs and classes are same. Only difference is that all members in structs are public.

Friend function and friend class

Friend functions and classes can access the private members of a class. In the following example printWeather() is friend of both Humidity and Temperature class. So it can access private members of both classes.

#include <iostream>
 
class Humidity;
 
class Temperature
{
private:
    int m_temp;
public:
    Temperature(int temp=0) { m_temp = temp; }
 
    friend void printWeather(const Temperature &temperature, const Humidity &humidity);
};
 
class Humidity
{
private:
    int m_humidity;
public:
    Humidity(int humidity=0) { m_humidity = humidity; }
 
    friend void printWeather(const Temperature &temperature, const Humidity &humidity);
};
 
void printWeather(const Temperature &temperature, const Humidity &humidity)
{
    std::cout << "The temperature is " << temperature.m_temp <<
       " and the humidity is " << humidity.m_humidity << '\n';
}
 
int main()
{
    Humidity hum(10);
    Temperature temp(12);
 
    printWeather(temp, hum);
 
    return 0;
}

Classes can also be friend of another class. In following example Display class if a friend of Storage class so it can access the private members.

#include <iostream>
 
class Storage
{
private:
    int m_nValue;
    double m_dValue;
public:
    Storage(int nValue, double dValue)
    {
        m_nValue = nValue;
        m_dValue = dValue;
    }
 
    // Make the Display class a friend of Storage
    friend class Display;
};
 
class Display
{
private:
    bool m_displayIntFirst;
 
public:
    Display(bool displayIntFirst) { m_displayIntFirst = displayIntFirst; }
 
    void displayItem(const Storage &storage)
    {
        if (m_displayIntFirst)
            std::cout << storage.m_nValue << " " << storage.m_dValue << '\n';
        else // display double first
            std::cout << storage.m_dValue << " " << storage.m_nValue << '\n';
    }
};
 
int main()
{
    Storage storage(5, 6.7);
    Display display(false);
 
    display.displayItem(storage);
 
    return 0;
}

Operator Overloading

In C++ each operator is actually a function. For example the operator + is acturally operator+() function.

  • At least one of the operand will have to be a custom type

Using Friend Function

#include <iostream>

class Point{
private:
    using point_t = int;
    point_t m_x;
    point_t m_y;

public:
    Point(): m_x(0), m_y(0){}
    Point(point_t x, point_t y): m_x(x), m_y(y){}

    // constant memeber function
    void print(void) const {
        std::cout << "(" << m_x << ", " << m_y << ")" << "\n";
    }

    // This is not a member function only a friend function
    // This could defined outside of the function too
    friend Point operator+(cost Point &p, const int n){
        return Point(p.m_x + n, p.m_y + n); 
    }

    friend Point operator+(const int n, const Point &p){
        return p + n;
    }

    friend Point operator+(const Point &p1, const Point &p2){
        return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y);
    }
};

int main(int argc, char *argv[]){
    Point p1(10, 20);
    Point p2(4, 5);
    Point p3 = p1 + p2 + 5;

    p3.print();

    return 0;
}

Using Normal Function

If there is no need to access private class data, operator overloading can be done as normal function.

#include <iostream>

class Point{
private:
    using point_t = int;
    point_t m_x;
    point_t m_y;

public:
    Point(): m_x(0), m_y(0){}
    Point(point_t x, point_t y): m_x(x), m_y(y){}

    point_t getX(void) const {
        return m_x; 
    }
    
    point_t getY(void) const {
        return m_y; 
    }

    // constant memeber function
    void print(void) const {
        std::cout << "(" << m_x << ", " << m_y << ")" << "\n";
    }
};

Point operator+(const Point &p, const int n){
    return Point(p.getX() + n, p.getY() + n); 
}

Point operator+(int n, const Point &p){
    return p + n;
}

Point operator+(const Point &p1, const Point &p2){
    return Point(p1.getX() + p2.getX(), p1.getY() + p2.getY());
}

int main(int argc, char *argv[]){
    Point p1(10, 20);
    Point p2(4, 5);
    Point p3 = p1 + p2 + 5;

    p3.print();

    return 0;
}

Using Member Function

The assignment (=), subscript ([]), function call (()), and member selection (->) operators must be overloaded as member functions. IO operators(<< and >>) can't be overloaded as memeber functions.

#include <iostream>

class Point{
private:
    using point_t = int;
    point_t m_x;
    point_t m_y;

public:
    Point(): m_x(0), m_y(0){}
    Point(point_t x, point_t y): m_x(x), m_y(y){}

    // No need to pass the class explicitly as it will be passed as hidden this pointer
    Point operator+ (int n){
        return Point(m_x + n, m_y +n);
    }
};

int main(int argc, char *argv[]){
    Point p1(10, 20);
    Point p2 = p1 + 5;
    return 0;
}

Overloading IO operators

#include <iostream>

class Point{
private:
    using point_t = int;
    point_t m_x;
    point_t m_y;

public:
    Point(): m_x(0), m_y(0){}
    Point(point_t x, point_t y): m_x(x), m_y(y){}

    point_t getX(void){
        return m_x; 
    }
    
    point_t getY(void){
        return m_y; 
    }

    friend std::ostream& operator<< (std::ostream &out, const Point &p);
    friend std::istream& operator>> (std::istream &in, Point &p);
};

// Overloading as friend function
// Returning std::ostream so that it can be chained like std::cout << p1 << p2
std::ostream& operator<< (std::ostream &out, const Point &p){
    out << "(" << p.m_x << ", " << p.m_y << ")";
    return out;
}

std::istream& operator>> (std::istream &in, Point &p){
    in >> p.m_x >> p.m_y;
    return in;
}

// Overloading as normal function
Point operator+ (Point p, int n){
    return Point(p.getX() + n, p.getY() + n); 
}

Point operator+ (int n, Point p){
    return p + n;
}

Point operator+ (Point p1, Point p2){
    return Point(p1.getX() + p2.getX(), p1.getY() + p2.getY());
}

int main(int argc, char *argv[]){
    Point p1(10, 20);
    Point p2;

    std::cout << "Enter a point:\n";
    std::cin >> p2;
    std::cout << p1 << " + " << p2 << " = " << p1 + p2 << "\n"; 

    return 0;
}

Functors

When classes acts like function calls they are called functors. This can be done by overloading () operator.

#include <iostream>

class Point{
private:
    using point_t = int;
    point_t m_x;
    point_t m_y;

public:
    Point(): m_x(0), m_y(0){}
    Point(point_t x, point_t y): m_x(x), m_y(y){}

    point_t getX(void){
        return m_x; 
    }
    
    point_t getY(void){
        return m_y; 
    }

    // () can only be overloaded as member function
    Point operator() (int n){
        return Point(m_x + n, m_y + n);
    }

    friend std::ostream& operator<< (std::ostream &out, const Point &p);
};

// Overloading as friend function
// Returning std::ostream so that it can be chained like std::cout << p1 << p2
std::ostream& operator<< (std::ostream &out, const Point &p){
    out << "(" << p.m_x << ", " << p.m_y << ")";
    return out;
}

int main(int argc, char *argv[]){
    Point pnt(10, 20);

    // Will add 5 to pnt.m_x, pnt.m_y and create new Point object.
    // Notce class object is called like function.
    std::cout << pnt(5) << "\n";

    return 0;
}

Overloading Typecast

Typecast overloading can be done to convert between types.

#include <iostream>
#include <cmath>

class Polar{
private:
    double m_r;
    double m_theta;

public:
    Polar(double r, double theta): m_r(r), m_theta(theta){}
    friend std::ostream& operator<< (std::ostream &out, const Polar &p);
};

// Overloading as friend function
// Returning std::ostream so that it can be chained like std::cout << p1 << p2
std::ostream& operator<< (std::ostream &out, const Polar &p){
    out << "(" << p.m_r << ", " << p.m_theta << ")";
    return out;
}

class Cartesian{
private:
    using point_t = int;
    point_t m_x;
    point_t m_y;

public:
    Cartesian(): m_x(0), m_y(0){}
    Cartesian(point_t x, point_t y): m_x(x), m_y(y){}

    friend std::ostream& operator<< (std::ostream &out, const Cartesian &c);

    // Typecast overloading
    operator Polar() const {
        double r = sqrt(pow(m_x, 2) + pow(m_y, 2)); 
        double theta = atan(static_cast<double>(m_y) / static_cast<double>(m_x));
        theta = (theta * 180) / M_PI;

        return Polar(r, theta);
    }
};

std::ostream& operator<< (std::ostream &out, const Cartesian &c){
    out << "(" << c.m_x << ", " << c.m_y << ")";
    return out;
}

int main(int argc, char *argv[]){
    Cartesian cart(10, 20);
    Polar pol(cart);

    std::cout << "Cartesian: " << cart << "\tPolar: " << pol << "\n";

    return 0;
}

Copy Constructors

When copy initialization is used a copy constructor is used. By default compiler uses memberwise initialization if no copy constructor is provided. For example:

class Xyz {
private:
    int m_var;
public:
    Xyz(int x): m_var(x){}
};

int main(void){
    Xyz xy(10);     // uses default initialization 
    
    Xyz yz(xy);     // Copy initialization
                    // As there is no copy constructor provided, compiler will do a memberwise initialization
}

Member functions of a class can access the private members ot the same class type. Here is an example with copy constructor:

class Xyz {
private:
    int m_var;
public:
    Xyz(int x): m_var(x){}

    // Copy constructor
    Xyz(const Xyz &xyz): m_var(xyz.m_var){}
};

int main(void){
    Xyz xy(10);     // uses default initialization 
    
    Xyz yz(xy);     // uses copy constructor
}

Elision

Copy initialization should be avoided as it can be elided for optimization.

#include <iostream>
#include <string>

class Hello{
    private:
        std::string m_s;

    public:
        Hello(std::string s): m_s(s){}

        Hello(const Hello &h): m_s(h.m_s){
            std::cout << "Copy constructor called\n";
        }

        std::string get(void){return m_s;}
};

int main(int argc, char *argv[]){
    Hello h("hello");
    Hello g(h);                 // Copy constructor will be used
    Hello i = Hello("gello");   // Copy constructor won't be used
    Hello k(Hello("gello"));    // Copy constructor won't be used

    std::cout << h.get() << g.get() << i.get() << k.get() << "\n";
}

If a class is passed by value in a function and return by value from a function copy constructor will be called. For example:

Hello changeHello(Hello h){ // Copy constructor will be called 
    h.change("new hello");
    return h;               // Copy constructor will be called
}

Implicit conversion, explicit and delete keyword

  • explicit keyword can be used to prevent implicit conversion.
  • delete keyword can be used to prevent both implicit and explicit conversion.
  • For details

Overloading assignment operator

  • Can only be oveloaded as memeber function.
  • Watch out for self assignment.
  • If no overloaded assignment operator is provided, compiler will do memberwise copy.
#include <iostream>
#include <string>

class Hello{
    private:
        std::string m_s;

    public:
        Hello(std::string s): m_s(s){}

        Hello(const Hello &h): m_s(h.m_s){
            std::cout << "Copy constructor called\n";
        }

        std::string get(void){return m_s;}

        Hello& operator= (const Hello &h){
            // If self copying
            if(this == &h) 
                return *this;   // for chainig

            m_s = h.m_s;

            return *this;   // for chaining
        }
};

int main(int argc, char *argv[]){
    Hello h("hello");
    Hello i("iello");
    Hello j("jello");
    Hello k(h);         // Copy constructor is called

    j = i = h;          // Overloaded function is called

    std::cout << h.get() << i.get() << j.get() << k.get() << "\n";
}

Shallow copy VS Deep copy

  • Shallow copy means memberwise copying by the compiler if no copy constructor or overloaded assingment operator is provided.
  • The default copy constructor and default assignment operators do shallow copies, which is fine for classes that contain no dynamically allocated variables.
  • Classes with dynamically allocated variables need to have a copy constructor and assignment operator that do a deep copy.

Object Relationship

Composition and Aggregation

In both cases relationship between parent and child is 'has a'.

Composition

  • The part (member) is part of the object (class)

  • The part (member) can only belong to one object (class) at a time

  • The part (member) has its existence managed by the object (class)

  • The part (member) does not know about the existence of the object (class)

  • Typically use normal member variables

  • Can use pointer members if the class handles object allocation/deallocation itself

  • Responsible for creation/destruction of parts

Aggregation

  • The part (member) is part of the object (class)

  • The part (member) can belong to more than one object (class) at a time

  • The part (member) does not have its existence managed by the object (class)

  • The part (member) does not know about the existence of the object (class)

  • Typically use pointer or reference members that point to or reference objects that live outside the scope of the aggregate class

  • Not responsible for creating/destroying parts

Association

  • The associated object (member) is otherwise unrelated to the object (class)
  • The associated object (member) can belong to more than one object (class) at a time
  • The associated object (member) does not have its existence managed by the object (class)
  • The associated object (member) may or may not know about the existence of the object (class)

Dependencies

A dependency occurs when one object invokes another object’s functionality in order to accomplish some specific task. This is a weaker relationship than an association, but still, any change to object being depended upon may break functionality in the (dependent) caller. A dependency is always a unidirectional relationship.

Container Classes

Hold and organize multiple instance of another type(class or fundamental type).

std::initializer_list

Used in container class's constructor for list initialization.

Inheritance

Order of construction

Most base class constructed first and most derived class constructed last.

#include <iostream>

class Parent{
public:
    Parent(){
        std::cout << "A" << "\n";
    }
};

class Child: public Parent{
public:
    Child(){
        std::cout << "B" << "\n";
    }
};

int main(int argc, char *argv[]){
    Child c;
    return 0;
}

/*
 * Will print:
 * A
 * B
 */

When a derived class is instanciated following things happen in order:

  • Memory for derived is set aside (enough for both the Base and Derived portions)
  • The appropriate Derived constructor is called
  • The Base object is constructed first using the appropriate Base constructor. If no base constructor is specified, the default constructor will be used.
  • The initialization list initializes variables
  • The body of the constructor executes
  • Control is returned to the caller

Constructors and initialization of derived classes

#include <iostream>

class Parent{
public:
    int m_x;
    
    Parent(int x=0): m_x(x){
        std::cout << "A" << "\n";
    }
};

class Child: public Parent{
public:
    int m_y;
    
    // Parent will be initialized with defautl vlaue
    Child(int y=0): m_y(y){
        std::cout << "B" << "\n";
    }

    // Parent will be initialized with given value
    Child(int x, int y): Parent(x), m_y(y){
        std::cout << "B" << "\n";
    }
};

int main(int argc, char *argv[]){
    // Parent's default constructor will be called
    Child c(10);

    // Parent will be initialized with given value
    Child d(20, 10);

    // Will print
    // Parent 0
    // Child 10
    std::cout << "Parent " << c.m_x << "\nChild " << c.m_y << "\n";
    
    // Will print
    // Parent 20
    // Child 10
    std::cout << "Parent " << d.m_x << "\nChild " << d.m_y << "\n";

    return 0;
}

When Child d(10, 20) is called following things happended:

  • Memory for Child is allocated.
  • The Child(int, int) constructor is called, where x = 10, and y = 20
  • The compiler looks to see if we’ve asked for a particular Base class constructor. We have! So it calls Parent(int) with x = 10.
  • The base class constructor initialization list sets m_x to 10
  • The base class constructor body executes, which prints A
  • The base class constructor returns
  • The derived class constructor initialization list sets m_y to 20
  • The derived class constructor body executes, which prints B
  • The derived class constructor returns

Order of Destruction

Destructors are called in reverse order of construction.

Inheritance and Access Specifiers

  • public: Can be accessed by anybody.
  • protected: Can be accessed by class member functions, friend functions and derived classes.
  • private: Can be accessed by only class member functions and friend functions.

A summary of the behavious when inherited publicly, protectedly or privately:

Access Specifier in Base Class Inherited Publicly Inherited Protectedly Inherited Privately
public public protected private
protected protected protected private
private inaccessible inaccessible inaccessible

Overriding Behavior

Redefining

#include <iostream>

class Base{
public:
    Base(){}

    void identify(void){
        std::cout << "I am base\n";
    }
};

class Derived: public Base{
public:
    Derived(){}

    void identify(void){
        std::cout << "I am derived\n";
    }
};

int main(int argc, char *argv[]){
    Derived d;

    // Will print I am derived
    d.identify();

    return 0;
}
  • Redefined functions doesn't inherite access specification from parent.

Expanding Existing Functionality

#include <iostream>

class Base{
public:
    Base(){}

    void identify(void){
        std::cout << "I am base\n";
    }
};

class Derived: public Base{
public:
    Derived(){}

    void identify(void){
        // if scope isn't used Derived::identify() will be called
        Base::identify();
        std::cout << "I am derived\n";
    }
};

int main(int argc, char *argv[]){
    Derived d;

    // Will print
    // I am base
    // I am derived
    d.identify();

    return 0;
}

Changing an Inherited Member's Access Specification

#include <iostream>

class Base{
public:
    Base(){}

protected:
    // Only membes, friends and derived class can call
    void identify(void){
        std::cout << "I am base\n";
    }
};

class Derived: public Base{
public:
    Derived(){}

    // Base::identify() is now public
    using Base::identify;
};

int main(int argc, char *argv[]){
    Base b;
    Derived d;

    // Error: can't call from here
    b.identify();

    // OK: as it is public in Derived class
    d.identify();

    return 0;
}

Functionality can be hidden by making it private in derived class.

#include <iostream>

class Base{
public:
    Base(){}

    void identify(void){
        std::cout << "I am base\n";
    }
};

class Derived: public Base{
public:
    Derived(){}

private:
    // Base::identify() is now private
    using Base::identify;
};

int main(int argc, char *argv[]){
    Base b;
    Derived d;

    // OK: as it is public in Base class.
    b.identify();

    // OK: as it is public in Derived class
    d.identify();

    return 0;
}

Multiple Inheritance

  • Ambiguity can arise.
#include <iostream>
 
class USBDevice
{
private:
    long m_id;
 
public:
    USBDevice(long id)
        : m_id(id)
    {
    }
 
    long getID() { return m_id; }
};
 
class NetworkDevice
{
private:
    long m_id;
 
public:
    NetworkDevice(long id)
        : m_id(id)
    {
    }
 
    long getID() { return m_id; }
};
 
class WirelessAdapter: public USBDevice, public NetworkDevice
{
public:
    WirelessAdapter(long usbId, long networkId)
        : USBDevice(usbId), NetworkDevice(networkId)
    {
    }
};
 
int main()
{
    WirelessAdapter c54G(5442, 181742);
    std::cout << c54G.getID(); // Which getID() do we call?

    // Can be solved using scope
    std::cout << c54G.USBDevice::getID();
 
    return 0;
}
  • Diamond problem

Virtual Functions

Pointers to the Base Class of Derived Object

It is possible to create pointers to the base class of derived object. But the pointers won't be able to call member functions from the derived class.

#include <iostream>

class Base{
public:
    Base(){}

    void identify(void){
        std::cout << "I am base\n";
    }
};

class Derived: public Base{
public:
    Derived(){}

    void identify(void){
        std::cout << "I am derived\n";
    }
};

int main(int argc, char *argv[]){
    Derived *pd = new Derived();
    // Pointer to base of derived object
    Base *pb{pd};

    // Will call Derived::indentify()
    pd->identify();

    // Will call Base::identify()
    pb->identify();
    
    return 0;
}

Use case for this could be in functions which takes derived class as parameter. For each derived class a different functions have to be created. For example:

void report(Derived &d){
    d.identify();
}

void report(AnotherDerived &ad){
    ad.identify();
}

This problem can be solved using pointer/reference to base:

void report(Base &b){
    b.identify();
}

int main(int argc, char *argv[]){
    Derived d;
    AnotherDerived ad;

    report(d);
    report(ad);
}

But the problem is in both cases Base::identify() will be called. As pointer to base only can see memebers from base. This problem can be solved using virtual functions.

Polymorphism

A virtual function is a special type of function that, when called, resolves to the most-derived version of the function that exists between the base and derived class. This capability is known as polymorphism. A derived function is considered a match if it has the same signature (name, parameter types, and whether it is const) and return type as the base version of the function. Such functions are called overrides.

#include <iostream>

class Base{
public:
    Base(){}

    virtual void identify(void){
        std::cout << "I am base\n";
    }
};

class Derived: public Base{
public:
    Derived(){}

    void identify(void){
        std::cout << "I am derived\n";
    }
};

class AnotherDerived: public Derived{
public:
    AnotherDerived(){}

    void identify(void){
        std::cout << "I am another derived\n";
    }
};

int main(int argc, char *argv[]){
    AnotherDerived ad;
    Base *bp = &ad;

    // Both resolve to AnotherDerived::identify()
    ad.identify();
    bp->identify();

    Derived d;
    bp = &d;
    
    // Resolve to Derived::identity() as it is the most derived class in this case
    bp->identify();
        
    return 0;
}

Another example:

#include <iostream>
#include <string>

class Base{
public:
    Base(){}

    virtual std::string getName(void) const {return "Base";}
};

class Derived: public Base{
public:
    Derived(){}
    
    virtual std::string getName(void) const {return "Derived";}
};

class AnotherDerived: public Derived{
public:
    AnotherDerived(){}
    
    virtual std::string getName(void) const {return "AnotherDerived";}
};

void report(Base &b){
    std::cout << "I am " << b.getName() << "\n";
}

int main(int argc, char *argv[]){
    Derived d;
    AnotherDerived ad;
            
    report(d);
    report(ad);
    
    return 0;
}

If you want to call functions from base class in virtual function just use scope operator:

Derived d;
Base *bp = &d;

std::cout << bp->Base::getName() << "\n";
  • Return type have to match between virtual functions.
  • Never use virtual function in constructors and destructors.

override and final Specifiers

A derived virtual function is only considered override if functino signature and return type matches exactly between the virtual functions. override specifier enforces virtualization of a function. If signature and return type doesn't match the compiler will generate an error. If override is used, no need to specify virtual keyword.

Covariant Return Type

Destructors

In case of inheritance destructors should always make virtual. Specially if there is dynamically allocated memory involved. If a derived class is converted to a base class and then call delete, only base destructor will be called. If dynamic memory is allocated in derived class, this will leak memory.

Abstract Class, Pure Virtual Functions and Interface Class

Abstract class is a class wich is only used by the derived class. It can't be instantiated anywhere else.

A pure virtual function has no body at all. It is meant to be redefined in derived classes. A class with pure virtual functions is an abstract class means it can't be intantiated. A pure virtual function may or may not have a body.

virtual void doSomething() = 0

In interface class has no member variables. All the functions are pure virtual.

Virtual Base Class

  • Single Base class is shared by the derived classes.
  • Solves diamond problem.
  • Most derived class is responsible for constructing the virtual base class.

Example:

#include <iostream>

class PoweredDevice
{
public:
    PoweredDevice(int power)
    {
		std::cout << "PoweredDevice: " << power << '\n';
    }
};

class Scanner: virtual public PoweredDevice // note: PoweredDevice is now a virtual base class
{
public:
    Scanner(int scanner, int power)
        : PoweredDevice{ power } // this line is required to create Scanner objects, but ignored in this case
    {
		std::cout << "Scanner: " << scanner << '\n';
    }
};

class Printer: virtual public PoweredDevice // note: PoweredDevice is now a virtual base class
{
public:
    Printer(int printer, int power)
        : PoweredDevice{ power } // this line is required to create Printer objects, but ignored in this case
    {
		std::cout << "Printer: " << printer << '\n';
    }
};

class Copier: public Scanner, public Printer
{
public:
    Copier(int scanner, int printer, int power)
        : PoweredDevice{ power }, // PoweredDevice is constructed here
        Scanner{ scanner, power }, Printer{ printer, power }
    {
    }
};

Object Slicing

Derived d;
Base b(d); // b will get the Base part from d. It is called object slicing

// Will call Base::doSomething() even if it is a virtual function
b.doSomething()

Base &b(d);

// Will call Derived::doSomething if it is a virtual function as b in reference to d
bp->doSomething()
  • In general avoid slicing

std::reference_wrapper

Dynamic Casting

Dynamic casing is used for downcasting.

Workaround for Friend Functions

Only member functions can be virtualized. So friend functions can't be virtualized. For example:

#include <iostream>
class Base
{
public:
    Base() {}
 
    // Here's our overloaded operator<<
    friend std::ostream& operator<<(std::ostream &out, const Base &b)
    {
        // Delegate printing responsibility for printing to member function print()
        return b.print(out);
    }
 
    // We'll rely on member function print() to do the actual printing
    // Because print is a normal member function, it can be virtualized
    virtual std::ostream& print(std::ostream& out) const
    {
        out << "Base";
        return out;
    }
};
 
class Derived : public Base
{
public:
    Derived() {}
 
    // Here's our override print function to handle the Derived case
    virtual std::ostream& print(std::ostream& out) const override
    {
        out << "Derived";
        return out;
    }
};
 
int main()
{
    Base b;
    std::cout << b << '\n';
 
    Derived d;
    std::cout << d << '\n'; // note that this works even with no operator<< that explicitly handles Derived objects
 
    Base &bref = d;
    std::cout << bref << '\n';
 
    return 0;
}

Templates

Function Template

#include <iostream>

template <typename T>
T add(T x, T y){
    return x + y;
}

int main(int argc, char *argv[]){
    std::cout << add(1, 2) << "\n";
    std::cout << add(1.5, 2.1) << "\n";

    return 0;
}

For more than one type:

template <typename T1, typename T2>
void doSomething(T1 x, T1 y){

}

Class Template

#include <iostream>
#include <string>

template <class T>
class Value{
private:
    T m_x;

public:
    Value(T x): m_x(x){}

    T get(void){return m_x;}
};

int main(int argc, char *argv[]){
    Value<int> ival(10);
    Value<double> dval(10.25);
    Value<std::string> sval("hello");

    std::cout << "ival: " << ival.get() << " dval: " << dval.get() << " sval: " << sval.get()
        << "\n";
    return 0;
}

If template class definition and implementation are splitted into seperate files linker error can be generated. To solve this following can be done:

  • Definition and implementation in one file.
  • Implementation in *.inl file and #include in *.h file.

For more details

Template Non-Type parameters

#include <iostream>
 
template <class T, int size> // size is the non-type parameter
class StaticArray
{
private:
    // The non-type parameter controls the size of the array
    T m_array[size];
 
public:
    T* getArray();
    
    T& operator[](int index)
    {
        return m_array[index];
    }
};
 
// Showing how a function for a class with a non-type parameter is defined outside of the class
template <class T, int size>
T* StaticArray<T, size>::getArray()
{
    return m_array;
}
 
int main()
{
    // declare an integer array with room for 12 integers
    StaticArray<int, 12> intArray;
 
    // Fill it up in order, then print it backwards
    for (int count=0; count < 12; ++count)
        intArray[count] = count;
 
    for (int count=11; count >= 0; --count)
        std::cout << intArray[count] << " ";
    std::cout << '\n';
 
    // declare a double buffer with room for 4 doubles
    StaticArray<double, 4> doubleArray;
 
    for (int count=0; count < 4; ++count)
        doubleArray[count] = 4.4 + 0.1*count;
 
    for (int count=0; count < 4; ++count)
        std::cout << doubleArray[count] << ' ';
 
    return 0;
}

Template Specialization

If an exception is needed to make for an specific type.

#include <iostream>
#include <string>
#include <cstring>

char str[100];

template <class T>
T add(T x, T y){
    return x + y;
}

// If it is an const char*
template<>
const char *add(const char *s1, const char *s2){
    strcat(str, s1);
    strcat(str, s2);
    return str;
}

int main(int argc, char *argv[]){
    std::cout << add(1, 2) << "\n";
    std::cout << add(1.5, 2.2) << "\n";
    std::cout << add("hello", "world") << "\n";
    return 0;
}

Standard Template Library

Appendix A: Some Usefull Functions

  • decltype(s) - Query the type of s