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
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 error1
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 member1
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 class | Derived class |
---|
private will | not be inherited |
public will be | public |
protected will be | protected |
- Deriving from the existing class protected ly
Base class | Derived class |
---|
private will | not be inherited |
public will be | protected |
protected will be | protected |
- Deriving from the existing class privately
Base class | Derived class |
---|
private will | not be inherited |
public will be | private |
protected will be | private |
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
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 class1
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
operator | symbol |
---|
1. Scope resolution operator | :: |
2. Member selection operator | . |
3. Member selection through pointer to member operator | .* |
4. Conditional operator | ?: |
5. size of operator | sizeof |
6. typeid operator | typeid |
Operators that can be overloaded
# | Name | symbols |
---|
1 | Binary arithmetic | + , - , * , / , % |
2 | Unary Arithmetic | + , - , ++ ,-- |
3 | Assignment | = ,+= ,*= , /= ,-= ,%= ,&= ,|= ,^= |
4 | Bit wise | & , \| ,! ,>> ,<< ,~ ,^ |
5 | Dereferencing | -> |
6 | Dynamic memory allocation and deallocation | new , delete |
7 | Subscript | [] |
8 | Function call | () |
9 | Logical | && , || ,! |
10 | Relational | > ,< ,== ,!= ,<= ,>= |
11 | Others | >>= ,<<= |
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
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
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
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
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:
✨ 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:
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
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
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
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
Containers | Description | Header required |
---|
deque | This is a sequence container. This is a double ended queue | <deque> |
list | This is a sequence container. This is a linear list | <list> |
map | This belongs to associative container. It stores key/value pairs in which one key may be associated with two or more values | <map> |
multimap | This belongs to associative container. It stores key/value pairs in which one key may be associated with two or more values | <map> |
multiset | This belongs to associative container. This is a set in which each element is not necessarily unique | <set> |
priority_queue | This belongs to derived containers. This is a priority queue | <queue> |
queue | This belongs to derived containers. This is an ordinary queue | <queue> |
set | This belongs to associative container. This is a set in which each element is unique | <set> |
stack | This belongs to derived container. This is a stack | <stack> |
vector | This 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 )