Home C++ concepts covered most
Post
Cancel

C++ concepts covered most

Inheritance

  • It is a relation ship between two classes where one class child inherit from other class parent

    Polymorphism

  • Single entity can exist in two different forms
    • Example:
      • function overloading - two function exist with same name
      • virtual functions
      • operator overloading

        Encapsulation

  • Mechanism by which data and functions are packed together into a single unit called class
  • It provide data security

    Abstraction

  • Representing the necessary details without including the background details which are not needed for presentation
  • Helps in reducing the complexities in understanding the unnecessary details
    • Example: STL

Access specifiers

  • Public: Data members and member function can be accessed any where in the program
  • Private: Data members and member functions can only be accessed with this object. That is only member function can access data member
  • Protected: Can be accessed only by the member function and friends of the class, Also it can be accessed by child member function and friends derived from that class

Initialization of the variables

1
2
int a = 10;
int a(10);

This pointer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Test
{
public:
	// compiler changes to 
	// void display(Test* const this, const std::string& name)
	void display(const std::string& name)
	{
		std::cout << "name is " << name << "\n";
		// compiler changes to
		//std::cout << "name is " << this->name << "\n";
	}
};

int main()
{
	Test t;
	t.display("Bhavith");
	// compiler changes to 
	// display(&t, "Bhavith");

	Test* p = new Test;
	p->display("Bhavith");
	// compiler changes to
	// display(p, "Bhavith")
}

Types of member functions

Nested member functions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Test
{
public:
	void setData(int a)
	{
		m_a = a;
		display(); // nested memeber function
	}
	void display()
	{
		std::cout << m_a << "\n";
	}
	int getData()
	{
		display(); // nested member function
		return m_a;
	}
private:
	int m_a;
};

Overloaded member function

1
2
3
4
5
6
class Attitude
{
public:
	void display(void);
	void display(int); // overloaded function
};

Overloaded member function of two different classes

1
2
3
4
5
6
7
8
9
10
11
class Attitude1
{
public:
	void display();
};

class Attitude2
{
public:
	void display();
};

In above example both Attitude1 and Attitude2 has function display with same prototype and it is completely valid

Member function with default argument

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Test()
{
public:
	int add(int a, int b = 20)
	{
		return a + b;
	}
};

int main()
{
	Test t;
	t.add(1, 2); // return 3
	t.add(1); // return 1 + 20 i.e. 21
}

Note: default argument should always starts from the right side of the function and cannot come in between or start of the function

Inline member function

Can define outside the class
1
2
3
4
5
6
7
8
9
class Test
{
public:
	int add (int a, in b);
};
inline int Test::add(int a, int b)
{
	return a + b;
}
Can define inside the class -> but it is by default inline
1
2
3
4
5
6
7
8
class Test
{
public:
	int add(int a, int b) // Automatically inline
	{
		return a + b;
	}
}
  • It is request to the compiler and a command
  • It can be added only to a small function
  • It pushes argument to the stack, saves various registers etc.

Constant member functions

It is kind of a function which denied permission to change the value of the data members

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Test
{
public:
	// compiler changes to 
	// void increment(const Test* const this)
	// In above line value is also a constant so we cannot modifies it
	void increment() const
	{
		m_value++; // not allowed
		// compiler
		// this->m_value; // not allowed because it is a const

		// But there is hack we can do i.e.
		((Test*)this)->m_value++;
		// here we are typecasting the const this pointer to non const this pointer and accessing it
	}
private:
	int m_value;
};

int main()
{
	Test t;
	t.increment();
}

There are three important concepts which we need to understand

  • constant data member
    • Member whose value cannot be changed throughout the execution of the program Key points:
      • In C const is a global that means it can be accessed in other file, if we want to make them local then prefix with static keyword
      • In C++ const is a local variable to that file and it cannot be accessed by any other file so explicit static is not required
      • In C++ const variables must be initialized during the creation other wise compiler throws error
        1
        2
        
        const int SIZE = 25;
        int arr[SIZE]; // allowed in C++ not in C
        
  • Mutable data member
    • If data member if prefixed with mutable key word then const member function can modify such data member
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      
      class Test
      {
      public:
        void increment() const
        {
        m_value++; // allowed since m_value is mutable
        }
      private:
        mutable int m_value;
      };
      
  • Static data member
    • It is not owned by any object of a class, but it is essentially a data member of a class
    • There is only one copy of a static data member created for a class which can be accessed by all the object of that class
    • Scope of the static member is within a class, but its lifetime is the entire program
    • Static data member is initialized to zero when it is created, static variables are used to maintain values common to the entire class
    • Static data members does not form a class size. This is because the static data member are created separately and not when the object is created.

Static Member functions

  • It is used to access or modify the static members (functions or variables)
  • It can be called without using an existing object using scope resolution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Test
{
public:
	static void update()
	{
		m_value = m_value + 10;
	}
private:
	static int m_value;
};

int main()
{
	Test::update(); // No need to create an instance directly we can call it
	return 0;
}

Array of class Objects

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Test
{
public:
	void set(int value)
	{
		m_value = value;
	}
	int get() const
	{
		return m_value;
	}
private:
	int m_value;
};

int main()
{
	Test t[2];
	for (int i = 0; i < 2; i++) {
		t[i].set(10);
		std::cout << t[i].get() << "\n";
	}

	Test* p = new Test[2];
	delete[] p; // dont forget to use delete[] not delete

	return 0;
}

Passing objects to functions

Passing objects by value

  • Here only a copy of the object is passed to a function, changes made to that copy will not reflect into the object which is calling

    Passing objects by reference

  • Here changes made to the object sent via function is reflected in the object which is calling

    Passing objects by pointer

  • Change of the object in the called function will be reflected in the calling function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Test
{
public:
	int a;
	void set(Test t, int value)
	{
		t.a = value;
	}
	
	void set(int value, Test& t)
	{
		t.a = value;
	}
	
	void set(Test* p, int value)
	{
		p->a = value;
	}
};

int main()
{
	Test t;
	t.a = 10;
	t.set(t, 11);
	std::cout << "pass by value " << t.a << "\n";
	t.set(12, t);
	std::cout << "pass by reference " << t.a << "\n";
	t.set(&t, 100);
	std::cout << "pass by pointer " << t.a << "\n";
}

Dynamic memory allocation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Test
{

};

int main()
{
	Test* p = new Test; // allocation 
	if (p) {
		delete p; // deallocation
	}

	// Array
	Test* q = new Test[10]; // allocation of array
	if (q) {
		delete[] q; // deallocation of array
	}
}

When ever heap is full then pointer returns nullptr. This can be checked using set_new_handler function which is declared in #include <new> header Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <new>
#include <cstdlib>

void my_handler()
{
	std::cout << "Memory exhaused !!!\n";
	exit(EXIT_FAILURE);
}

int main()
{
	set_new_handler(my_handler);
	while (1) {
		int* p = new int[1000000];
	}
	return 0;
}

Constructors and Destructors

Constructor

  • Member function of a class, whose name is same as class name
  • Task is to initialize the data member for an object of a class automatically, when an object of the same class is created
  • No return type for it
  • Can be overloaded
  • Cannot be a virtual function Types
    • Default constructor
    • Parameterized constructor
      • When parameterized constructer is defined then compiler will not add default constructor we need to create on explicitly
    • Parameterized dynamic constructor
    • Overloaded constructors
    • Constructors with default value
    • Copy constructors

Inheritance

  • Deriving from existing class publicly
Base classDerived class
private willnot be inherited
public will bepublic
protected will beprotected
  • Deriving from the existing class protected ly
Base classDerived class
private willnot be inherited
public will beprotected
protected will beprotected
  • Deriving from the existing class privately
Base classDerived class
private willnot be inherited
public will beprivate
protected will beprivate

Important points ⭐ - Constructors are not inherited but can be called from the derived class

![[single-inheritance.svg]]

Overriding

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
struct A
{
    void display()
    {
        std::cout << "Display of A \n";
    }
};

struct B : public A
{
    void display()
    {
        std::cout << "Display of B \n";
    }
};

int main()
{
	A a;
	a.display(); // Display of A because left side is a
	a.B::display(); // compilation error left side parent and it is a
	
	B b;
	b.display(); // Display of B because left side is b
	b.A::display(); // Display of A because scope resolution is used

	A* ap = new A;
	ap->display(); // Display of A becaus left side is A
	ap->B::display(); // compilation error left side is parent and B is not yet created

	B* bp = new B;
	bp->display(); // Display of B because left side of B pointer
	bp->A::display(); // Display of A because scope resolution is used to call parent display 

	A* p = new B;
	p->display(); // Display of A because no virtual function and left side is A pointer
	p->B::display(); // Compilation error because left side pointer is A type
}

Virtual function and polymorphism

  • Compile time polymorphism
    • function overloading
    • operator overloading
  • Run time polymorphism
    • virtual functions

How virtual functions work internally

  • During compile time a virtual table (VTBL) is created both for base class and derived class.
  • This table contains only the addresses of virtual function in the corresponding class
  • Addresses of non virtual functions is not present in VTBL Example
    1
    2
    3
    4
    5
    6
    
    class A
    {
    public:
      virtual void xyz();
      virtual void pqr();
    };
    
  • During run time, the VTBL of the base class contains all the addresses of the functions xyz() and pqr() as shown below ![[Base A.svg]] Second, let us consider the VTBL of the derived class
    1
    2
    3
    4
    5
    6
    
    class B : public A
    {
    public:
      void pqr();
      virtual void jkl();
    };
    
  • If the derived class contains an overriding function, then the VTBL of the derived class contains a new address for the overriding function.
  • If a derived class declares a new virtual functions, its VTBL contains the address of the new virtual function.
  • If the derived class does not redefine a certain base class virtual function, then the VTBL of the derived class will contain the address of the inherited base class function itself ![[Base B.svg]] Whenever a virtual function is called, the value of VPTR is read first, then the address of the called function is obtained from the VTBL

Size of the object of classed with virtual function increases uniformly by four or eight bytes depends on architecture due to presence of additional pointer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>

#pragma pack(1)
class A
{
public:
    virtual void xyz()
    {
        std::cout << "xyz of A \n";
    }

    virtual void pqr()
    {
        std::cout << "pqr of A\n";
    }
};

#pragma pack(1)
class B : public A
{
public:
    void pqr()
    {
        std::cout << "pqr of B \n";
    }

    void jkl()
    {
        std::cout << "jkl of B\n";
    }
};

int main()
{
    A* ap = new A; // VTBL with VPTR is created for A
    ap->xyz(); // xyz of A
    ap->pqr(); // pqr of A

    B* bp = new B(); // VTBL with VPTR is created for B
    bp->xyz(); // xyz of A
    bp->pqr(); // pqr of B
    bp->jkl(); // jkl of B

    A* p = new B; // VTBL with VPTR is created for B
    p->pqr(); // pqr of B

	return 0;
}

Output

1
2
3
4
5
6
xyz of A 
pqr of A
xyz of A 
pqr of B 
jkl of B
pqr of B 

Virtual destructor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class A
{
public:
	A() 
	{
		std::cout << "A Ctor called \n";
	}
	~A()
	{
		std::cout << "A Dtor called \n";
	}
};

class B : public A
{
public:
	B()
	{
		std::cout << "B Ctor called \n";
	}
	~B()
	{
		std::cout << "B Dtor called \n";
	}
};

int main()
{
	B* bp = new B;
	delete bp; // A Ctor -> B Ctor -> B Dtor -> A Dtor

	A* ap = new A;
	delele ap; // A Ctor -> A Dtor

	A* p = new B;
	delete p; // A Ctor -> B Ctor -> A Dtor

	// Here is the problem there is not B Dctor called it is compile time resolution there we need to put virtual for Dtor then it works
}

Early Binding Vs Late Binding

Early Binding (Static Binding)
  • compiler binds function call and the appropriate function definition at compile time
    • function overloading
    • operator overloading
      Late binding (dynamic binding)
  • function call and the appropriate function definition is bind at run time this can be achieved by using virtual functions
  • There are run time performance overhead

Operator Overloading

We can overload

  • Unary operators (operating on one operand)
  • Binary operators (operating on two operands)
  • Special operators ([], (), etc)

Operators that cannot be overloaded

operatorsymbol
1. Scope resolution operator::
2. Member selection operator.
3. Member selection through pointer to member operator.*
4. Conditional operator?:
5. size of operatorsizeof
6. typeid operatortypeid

Operators that can be overloaded

#Namesymbols
1Binary arithmetic+, -, *, /, %
2Unary Arithmetic+, -, ++,--
3Assignment=,+=,*=, /=,-=,%=,&=,|=,^=
4Bit wise&, \|,!,>>,<<,~,^
5Dereferencing->
6Dynamic memory allocation and deallocationnew, delete
7Subscript[]
8Function call()
9Logical&&, ||,!
10Relational>,<,==,!=,<=,>=
11Others>>=,<<=

Unary operator syntax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<return-type> <class_name>:: operator opr ()
{
	// operator function definition
}
// Example
class Test
{
public:
	void operator - ()
	{
		// `-` is overloaded
	}
	void operator + ()
	{
		// `+` is overloaded
	}
};

Binary operator syntax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<return-type> <class_name>:: operator opr(<argument1>) // only one argument
{
	// function defintion
}
// Example
class Test
{
public:
	void operator + (Test& t)
	{
		// `+` is overloaded
	}

	void operator - (Test& t)
	{
		// `-` is overloaded
	}
};

Important overloaded operators which is used frequently

Overloading << and >> operator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>

class Point
{
public:
    Point(int x, int y) : m_x(x), m_y(y)
    {

    }

    ~Point()
    {
        m_x = 0;
        m_y = 0;
    }

    friend std::ostream& operator << (std::ostream& out, const Point& point)
    {
        out << "x = " << point.m_x << ", y = " << point.m_y;
        return out;
    }

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

private:
    int m_x;
    int m_y;
};


int main()
{
    Point p(10, 20);
    std::cin >> p;
    std::cout << p << "\n";
}

Overloading new and delete operator

Syntax:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//New
class <class_nam>
{
public:
	void* operator new (size_t)
	{
		// defintion
	}
};

// New array
class <class_name>
{
public:
	void* operator new [](size_t)
	{
		// definition
	}
};

// delete
class <class_name>
{
public:
	void operator delete(void*)
	{
	}
};

// delete array
class <class_name>
{
public:
	void operator delete[] (void*)
	{
	}
};

Examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <iostream>

class Point
{
public:

    void* operator new (size_t size)
    {
        std::cout << "Allocating memory\n";
        void* p = ::operator new(size);
        return p;
    }

    void* operator new [] (size_t size)
    {
        std::cout << "Allocating memory for array\n";
        void* p = ::operator new[](size);
        return p;
    }

    void operator delete (void* p)
    {
        std::cout << "Deleting pointer \n";
        if (p) {
            ::operator delete(p);
        }
    }

    void operator delete [] (void* p)
    {
        std::cout << "Deleting pointer array\n";
        if (p) {
            ::operator delete[](p);
        }
    }

    Point()
    {
        std::cout << "Ctor\n";
    }

    Point(int x, int y) : m_x(x), m_y(y)
    {
        std::cout << "Ctor\n";
    }

    ~Point()
    {
        std::cout << "Dtor\n";
        m_x = 0;
        m_y = 0;
    }

private:
    int m_x;
    int m_y;
};


int main()
{
    Point* p = new Point(10, 20);
    delete p;

    Point* pArr = new Point[2];
    delete[] pArr;
}

Overloading [] operator

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>

class Array
{
public:
    Array()
    {
        for (int i = 0; i < 10; i++) {
            m_arr[i] = i + 1;
        }
    }
    int operator [](int index)
    {
        std::cout << "Indexing ..\n";
        if (index < 0 || index >= 10) {
            return -1;
        }
        return m_arr[index];
    }
private:
     int m_arr[10];
};

int main()
{
    Array arr;
    std::cout << arr[8] << "\n";
}

Overloading = operator

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>

class Point
{
public:

     Point(int x, int y) : m_x(x), m_y(y)
     {

     }

     Point(const Point& other)
     {
         std::cout << "Copy ctor invoked\n";
         m_x = other.m_x;
         m_y = other.m_y;
     }

     void operator = (const Point& other)
     {
         std::cout << "= operator invoked\n";
         m_x = other.m_x;
         m_y = other.m_y;
     }

     void print()
     {
         std::cout << "x = " << m_x << ", y = "<< m_y << "\n";
     }

private:
    int m_x;
    int m_y;
};

int main()
{
    Point p1(10, 20);
    Point p2 = p1; // copy ctor
    p2.print();
    Point p3 (p1); // copy ctor
    p3.print();

    Point p4(100, 200);
    p1 = p4; // = operator is called
    p1.print();

    return 0;
}

Overloading () operator

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>

class Point
{
public:

     Point(int x, int y) : m_x(x), m_y(y)
     {

     }

     void operator()(int x , int y)
     {
         std::cout << "Paramertized opertor () is called \n";
         m_x = x;
         m_y = y;
         print();
     }

     void operator ()()
     {
        std::cout << "Opertor () is called \n";
        print();
     }

     void print()
     {
         std::cout << "x = " << m_x << ", y = "<< m_y << "\n";
     }

private:
    int m_x;
    int m_y;
};

int main()
{
    Point p(10, 20);
    p();
    p(100, 200);

    return 0;
}

Order of invocation

If we have 1 child class inherited from two base class then constructor is called on the order of inheritance not in order of invocation class Derived : public Base1, public Base2

1
2
3
- Base1 ctor called
- Base2 ctor called
- Derived Ctor called

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>

class Base1
{
public:
    Base1()
    {
        std::cout << "Base 1 ctor called \n";
    }
};

class Base2
{
public:
    Base2()
    {
        std::cout << "Base 2 ctor called\n";
    }
};

class Derived : public Base2, public Base1
{
public:
    Derived() : Base1(), Base2()
    {
        std::cout << "Derived ctor called \n";
    }
};


int main()
{
    Derived d;
    return 0;
}

Output

1
2
3
Base 2 ctor called
Base 1 ctor called 
Derived ctor called 

Event though we invoked Base1 constructor in Derived constructor, Ctor called in order of inheritance

Order of invocation virtual inheritance

In case of virtual inheritance, virtual class constructor will be called first, then order of inheritance Example class Derived : public Base2, public Base1, virtual public Base3

1
2
3
4
- Base3 ctor called
- Base2 ctor called
- Base1 ctor called
- Derived ctor called

example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>

class Base1
{
public:
    Base1()
    {
        std::cout << "Base 1 ctor called \n";
    }
};

class Base2
{
public:
    Base2()
    {
        std::cout << "Base 2 ctor called\n";
    }
};

class Base3
{
public:
    Base3()
    {
        std::cout << "Base 3 ctor called\n";
    }
};


class Derived : public Base2, public Base1, virtual public Base3
{
public:
    Derived() : Base1(), Base2()
    {
        std::cout << "Derived ctor called \n";
    }
};

int main()
{
    Derived d;
    return 0;
}

Output

1
2
3
4
Base 3 ctor called
Base 2 ctor called
Base 1 ctor called 
Derived ctor called 

Type conversion

  • conversion from basic type to class type
  • conversion from class type to basic type
  • conversion from one class type to another class type

Conversion from basic type to class type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
class Test
{
public:
    Test()
    {

    }
    Test(int value) : m_value(value) // constructor performing conversion
    {
        std::cout << "Parmeterized ctor is invoked \n";
    }
    void display()
    {
        std::cout << "value = " << m_value << "\n";
    }
private:
    int m_value;
};

int main()
{
    Test t;
    t = 10;
    t.display();
    return 0;
}

Output

1
2
Parmeterized ctor is invoked 
value = 10

Here t = 10; compiler try to find operator = it did not find anything then it finds the parametrized constructor and which can take int so it called that and compiler converts 10 to class type Test

Conversion from class type to basic type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
class Test
{
public:
    Test(int value) : m_value(value)
    {

    }

    operator int() // this does the conversion
    {
        return m_value;
    }

    void display()
    {
        std::cout << "value = " << m_value << "\n";
    }
private:
    int m_value;
};

int main()
{
    Test t(10);
    int x = t;
    std::cout << "x = " << x << "\n";
    return 0;
}

Output

1
x = 10

There are some rules to be remember when overloading the types First syntax:

1
2
3
4
opertor type()
{
	return <type value>;
}

Rules: - It must have no arguments - It must be a class member function - It must not specify a return type but should return a value

Conversion from class type to class type

Using parametrized constructor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>
class B
{
public:
    int getValue()
    {
        return m_valueOfB;
    }

private:
    int m_valueOfB = 100;
};

class A
{
public:
    A(B& b) // constructor converting the object of other type
    {
        std::cout << "Calling conversion \n";
        m_value = b.getValue();
    }

    A()
    {

    }

    ~A()
    {

    }

    void display()
    {
        std::cout << "value = " << m_value << "\n";
    }
private:
    int m_value = -1;
};

int main()
{
    A a;
    B b;
    a = b; // here it should have called operator = but since it is not there it has choose paramterized ctor
    a.display();
    return 0;
}

Output

1
2
Calling conversion 
value = 100

Using type conversion

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
class B
{
public:
    int getValue()
    {
        return m_valueOfB;
    }

private:
    int m_valueOfB = 100;
};

class A
{
public:

    operator B() // contains operator of other class. it is little confusing I know
    {
        B temp;
        m_value = temp.getValue();
        return temp;
    }

    void display()
    {
        std::cout << "value = " << m_value << "\n";
    }
private:
    int m_value = -1;
};

int main()
{
    A a;
    B b;
    b = a;
    a.display();
    return 0;
}

Output

1
value = 100

explicit 🌟

To avoid basic data type to class data type conversion we have to use explicit and it avoid implicit conversion from basic data type to class data type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>

class A
{
public:

    A(int x)
    {
        m_value = x;
    }
    void display()
    {
        std::cout << "value = " << m_value << "\n";
    }
private:
    int m_value = -1;
};

int main()
{
    A a = 10; // implicit conversion
    a.display();
    return 0;
}

Output

1
value = 10

How can we avoid it. just by adding explicit key word to parameterized constructor

1
2
3
4
explicit A(int x)
{
	m_value = x;
}

Compilation output

1
2
3
4
5
6
7
8
C:/Users/bhavi/Documents/learningcpp/main.cpp: In function 'int main()':
C:/Users/bhavi/Documents/learningcpp/main.cpp:21:11: error: conversion from 'int' to non-scalar type 'A' requested
   21 |     A a = 10;
      |           ^~
ninja: build stopped: subcommand failed.
19:00:44: The process "C:\Qt\Tools\CMake_64\bin\cmake.exe" exited with code 1.
Error while building/deploying project learningcpp (kit: Desktop Qt 6.3.0 MinGW 64-bit)
When executing step "Build"

RUN-TIME TYPE IDENTIFCATION (RTTI)

To identify which derived object the base pointer it pointing to C++ uses dynamic_cast and typeid During runtime to identify which type of object the base pointer is pointing we call as Run time type identification

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <typeinfo>

int main()
{
    int a;
    float b;
    double c;
    long d;

    std::cout << typeid(a).name() << "\n";
    std::cout << typeid(b).name() << "\n";
    std::cout << typeid(c).name() << "\n";
    std::cout << typeid(d).name() << "\n";
}

Output

1
2
3
4
i // means int
f // means float
d // means double
l // means long

Lets check with user defined data type and its derived

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <typeinfo>

class Parent
{

};

class Derived : public Parent
{

};

int main()
{
    Parent p;
    Derived d;
    std::cout << typeid(p).name() << "\n";
    std::cout << typeid(d).name() << "\n";
}

Output - output depends on OS to OS

1
2
6Parent
7Derived
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <typeinfo>

class Parent
{
public:
    void display()
    {

    }
};

class Derived : public Parent
{
public:
    void display()
    {

    }
};

int main()
{
    Parent *p = new Derived;
    std::cout << typeid(*p).name() << "\n";
}

Output:

1
6Parent

Here we can see that, since there is no virtual function in Parent class typeid is giving as Parent since the pointer is of Parent type

Let’s see what happen if we add virtual function in Parent

1
2
3
4
virtual void display()
{

}

Output:

1
7Derived

Now you can see we get Derived. That means typeid is used only for polymorphic classes Final example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <typeinfo>

class Parent
{
public:
    virtual void display()
    {

    }
};

class Derived : public Parent
{
public:
    void display()
    {

    }
};

int main()
{
    Parent *p = new Parent;
    Derived *d = new Derived;

    if (typeid(*p) != typeid(*d))
    {
        std::cout << "Correct\n";
    } else {
        std::cout << "Wrong\n";
    }

    p = d;
    if (typeid(*p) == typeid(*d))
    {
        std::cout << "Correct\n";
    } else {
        std::cout << "Wrong\n";
    }

}

Output

1
2
Correct
Correct

Worked as expected !!!

Casting operators

  • dynamic_cast operator
  • static_cast operator
  • reinterpret_cast operator
  • const_cast operator
dynamic_cast operator

When ever we want to convert the base pointer to derived pointer then dynamic_cast is used and it return pointer if success otherwise NULL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <typeinfo>

class Parent
{
public:
    virtual void display()
    {

    }
};

class Derived : public Parent
{
public:
    void display()
    {

    }
};

int main()
{
    Parent* parent; // parent pointer 
    Derived* derived = new Derived; // Derived object

    parent = derived; // parent can point to derived

	// I want to take back my pointer from parent, it's not possible during compile time. Below statement give compilation error
	
    //derived = parent;

	// Use run time casting 
    derived = dynamic_cast<Derived*>(parent);

    if (derived == NULL) {
        std::cout << "Dynamic cast failed\n";
    } else {
        std::cout << "Success\n";
    }

    delete derived;
}

Output

1
Success
static_cast operator

it is same as dynamic_cast but does not return any error, If we use it wrong way then it produce undefined behaviour

reinterpret_cast operator

It is same as C type casting

1
2
3
4
5
6
int i = 6;
int *a = &i;
void* p = a;
int* q ;
q = (int*)p; // C style
q = reinterpret_cast<int*>(p); // C++ style 
const_cast operator

If we want to change the value of the member variable in const function then we have following options

  • Make data member as mutable
  • Type cast and remove const -> This is called const cast with syntactical sugar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <typeinfo>

class Test
{
public:
     void increment() const
     {
         // type cast myself
         ((Test*)this)->m_value++;

         //Using const cast
        const_cast<Test*>(this)->m_value++;
     }
    int m_value = 0;
};

int main()
{
    Test t;
    t.increment();
    std::cout << t.m_value << "\n";
}

Output

1
2

Templates

Function templates

1
2
3
4
5
template<class T>
return_type <function_name> (paremetees of type T)
{
	// function body
}

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>

// example 1
template<class T>
T add(T a, T b)
{
    std::cout << "using example 1 \n";
    return a + b;
}

// example 2
template<class A, class B>
B add(A v1, B v2)
{
    std::cout << "using example 2 \n";
    return v1 + v2;
}

// example 3
template<class T>
T updateBy(T a, int b)
{
    std::cout << "using example 3 \n";
    return a + b;
}

int main()
{
    // example 1
    std::cout << add<int>(10, 20) << "\n";
    std::cout << add<double>(10.1, 20.2) << "\n";

    // example 2
    std::cout << add<int, int>(10, 20) << "\n";
    std::cout << add<int, double>(10, 20.2) << "\n";

    // example 3
    std::cout << updateBy<int>(10, 5) << "\n";
    std::cout << updateBy<float>(0.5, 5) << "\n";
}

Output

1
2
3
4
5
6
7
8
9
10
11
12
using example 1 
30
using example 1 
30.3
using example 2 
30
using example 2 
30.2
using example 3 
15
using example 3 
5.5

Class Templates

1
2
3
4
5
template<class T>
class class_name
{
	// class definition		
};

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>

template <class T>
class Add
{
public:
    T add(T a, T b)
    {
        std::cout << "example 1\n";
        return a + b;
    }
};

template <class A, class B>
class Point
{
public:
    B addCordinates(A a, B b)
    {
        std::cout << "example 2\n";
        return a + b;
    }
};

template <class T>
class Update
{
public:
    int updateBy10(T value, int a = 10)
    {
        std::cout << "example 3\n";
        return value + 10;
    }
};

int main()
{
    // example 1
    Add<int>a;
    std::cout << a.add(2, 3) << "\n";

    // example 1
    Add<float>b;
    std::cout << b.add(2.5, 10.0) << "\n";

    // example 2
    Point<int, int> p;
    std::cout << p.addCordinates(10, 20) << "\n";

    // example 2
    Point<int, double> q;
    std::cout << q.addCordinates(10, 20.25) << "\n";


    // example 3
    Update<int> u;
    std::cout << u.updateBy10(20) << "\n";

    Update<double> v;
    std::cout << v.updateBy10(20.35) << "\n";
}

Standard Template Library

There are three important element in STL, they are

  • Containers: Containers are template class objects that represent a type of data structure and are used to hold data elements of the same type
  • Algorithms: Algorithms are readily available functions which are used with containers to preform specific operations on the data elements present in the container Example: count(), fill(), swap(), sort(), merge(), equal()
  • Iterators: An iterator is a pointer that points to specific data in the container. it helps in inserting and deleting the data from a container.

Container are classified into 3 types they are - Sequential containers - Associative containers - Derived containers There are 10 different containers available in STL, which are distributed among the mentioned categories of containers

ContainersDescriptionHeader required
dequeThis is a sequence container. This is a double ended queue<deque>
listThis is a sequence container. This is a linear list<list>
mapThis belongs to associative container. It stores key/value pairs in which one key may be associated with two or more values<map>
multimapThis belongs to associative container. It stores key/value pairs in which one key may be associated with two or more values<map>
multisetThis belongs to associative container. This is a set in which each element is not necessarily unique<set>
priority_queueThis belongs to derived containers. This is a priority queue<queue>
queueThis belongs to derived containers. This is an ordinary queue<queue>
setThis belongs to associative container. This is a set in which each element is unique<set>
stackThis belongs to derived container. This is a stack<stack>
vectorThis belongs to sequential container This is a dynamic array<vector>

There are many more added in modern cpp checkout https://www.cplusplus.com/reference/stl/

Container class templates

Sequence containers:

1
2
3
4
5
[**array**](https://www.cplusplus.com/reference/array/array/) Array class (class template )
[**vector**](https://www.cplusplus.com/reference/vector/vector/)Vector (class template )
[**deque**](https://www.cplusplus.com/reference/deque/deque/)Double ended queue (class template )
[**forward_list**](https://www.cplusplus.com/reference/forward_list/forward_list/) Forward list (class template )
[**list**](https://www.cplusplus.com/reference/list/list/)List (class template )

Container adaptors:
stackLIFO stack (class template ) queueFIFO queue (class template ) priority_queuePriority queue (class template )

Associative containers:
setSet (class template ) multisetMultiple-key set (class template ) mapMap (class template ) multimapMultiple-key map (class template )

Unordered associative containers:
unordered_set Unordered Set (class template ) unordered_multiset Unordered Multiset (class template ) unordered_map Unordered Map (class template ) unordered_multimap Unordered Multimap (class template )

This post is licensed under CC BY 4.0 by the author.

Never forget to delete an array of object using delete[]

Single Responsibility Principle