Oop Cheatsheet: Access Specifiers
Oop Cheatsheet: Access Specifiers
Definition
Access Specifiers
Public Members are accessible from outside the class
Private Members cannot be accessed from outside the class
Protected Like private, except members can be accessed in friend/inherited classes
Encapsulation
Encapsulation is defined as wrapping up related data and functions that operate on them under
a single unit, thereby providing: (a) security in the form of data hiding and (b) making the code
more readable and reusable in the long run.
Example: Departments within a company can have their own implementation of a generic parent
Department() class, making each department’s data hidden from the others, unless accessed
by an object of that class. Now there may arise a situation when for some reason an official from
one dept. X needs all the data about some other dept. Y in a particular month. In this case, X is
not allowed to directly access the data of the Y section. Thus, X will first have to contact some
other officer in the Y dept. and then request him to give the particular data.
Abstraction
Abstraction means displaying only essential information and hiding the details. Data abstraction
refers to providing only essential information about the data to the outside world, hiding the
background details or implementation. Abstraction is achieved in C++ in two ways:
● Abstraction using Classes: We can implement Abstraction in C++ using classes. The
class helps us to group data members and member functions using available access
specifiers. A Class can decide which data member will be visible to the outside
world(public) and which is not(private).
● Abstraction in Header files: Not essentially a feature of OOP, because this has been
there since before OOP was introduced, but still very useful in achieving encapsulation.
Polymorphism
Ability to take more than one form is called polymorphism. In an OOP environment, an
operation may exhibit different behaviours in different instances. The behaviour depends upon
the types of data used in the operation/passed as argument. C++ supports Operator
Overloading and Function Overloading.
Operator Overloading
In C++, can be done by using operator keyword followed by operator_name.
Example
Consider the Complex Class, shown below:
class Complex {
private:
int real, imag;
public:
Complex(int r = 0, int i =0) { //Constructor
real = r; imag = i;
}
void print() {
cout << real << " + i" << imag << endl;
}
};
Now, if we want to add two complex numbers, we cannot use the + operator as-is, because the
compiler does not know how complex numbers work. Hence, we must overload the ‘+’ Operator
as shown:
Complex operator + (Complex const &obj) {
Complex res;
res.real = real + obj.real;
res.imag = imag + obj.imag;
return res;
}
//if we have c1 and c2, and we do c1 + c2, c1 is res and c2 is obj
Operators which cannot be overloaded:
. : Dot Operator
:: : Scope resolution
?: : Conditional operator
sizeof : Get size
Function Overloading
Function overloading is when two or more functions can have the same name but accept
different parameters. The function can perform different operations and hence it eliminates the
use of different function names for the same kind of operations.
Note
Function overloading (achieved in compile time) is different from Function Overriding (achieved
at run time). Function Overriding is the redefinition of a base class function in its derived class
with the same signature i.e return type and parameters.
Inheritance
Inheritance is a mechanism in which one object acquires all the properties and behaviors of a
parent class, in addition to the already existing members of that class. The idea behind
inheritance is that we can create new classes that are built upon existing classes.
Modes of Inheritance
● Public mode: If we derive a subclass from a public base class.
○ Public(BaseClass) := Public(Subclass)
○ Protected (BaseClass) := Protected (Subclass)
class Node {
private:
int key;
Node* next;
// Now class LinkedList can access private members of Node
friend class LinkedList;
};
Friend Function
Like friend class, a friend function can be given a special grant to access private and protected
members. A friend function can be:
● A member of another class or
● A global function
class Node {
private:
int key;
Node* next;
/* makes search() of LinkedList class a friend function */
friend int LinkedList::search();
};
Note
● Friendship is not mutual. If class A is a friend of B, then B doesn’t become a friend of A.
● Friendship is not inherited.
● If too many functions or external classes are declared as friends of a class with protected
or private data, it lessens the value of encapsulation.
Virtual Functions
A virtual function is a member function which is declared within a base class using the virtual
keyword. It may be re-defined(overridden) by a derived class as well.
Example: Class Car may have some method openDoor which can be overridden in some
sub-class SuperCar. In that case, we have to make openDoor a virtual function in Car
● Thus, upon referring to a derived class object using a pointer or a reference to the base
class, you can call a virtual function for that object and execute the derived class’s
version of the function.
● Thus, virtual functions ensure that the correct function is called for an object, regardless
of the type of reference (or pointer) used for function call. Since the resolving of function
calls is done at run-time, virtual functions are mainly used to achieve runtime
polymorphism.
To achieve run-time polymorphism using virtual functions, there are some caveats:
I. Virtual functions should be accessed using pointer or reference of base class type to
achieve runtime polymorphism.
II. Virtual functions cannot be static.
III. The prototype of virtual functions should be the same in the base as well as derived
class.
IV. A class may have a virtual destructor but it cannot have a virtual constructor.
Consider a Parent class and a Child class, which is derived from Parent. The compiler
maintains two things to serve this purpose:
● vtable: A table of virtual function pointers, maintained per class.
● vptr: A pointer to vtable, maintained per object instance.
Compiler adds additional code at two places to maintain and use vptr.
1. Code in every constructor This code sets the vptr of the object being created. This
code sets vptr to point to the vtable of the class.
2. Code with polymorphic function call Wherever a polymorphic call is made, the
compiler inserts code to first look for vptr using base class pointer or reference (Since
pointed or referred object is of derived type, vptr of derived class is accessed). Once
vptr is fetched, vtable of the derived class can be accessed. Using vtable, the
address of the derived class function is accessed and called.
Constructors
Syntax: ClassName()
The name of the constructor is the same as the name of the class, and constructors don’t have
a return type. Constructors can be defined either inside the class definition or outside class
definition using class name and scope resolution :: operator. Constructors can be
overloaded as well, as long each definition takes in different arguments.
Important:
Why should the copy constructor accept its parameter by reference in C++?
Because if it's not by reference, it's by value. To do that we again have to make a copy, and to
do that we call the copy constructor. But to do that, we need to make a new value, so we call the
copy constructor again, thus leading to an infinite chain of recursive calls.
Diamond Problem
The diamond problem occurs when two superclasses of a class have a common base class.
Consider the following case, where both B and C are derived from A, and D inherits from both B
and C:
A A
| |
B C
\ /
D
In the above program, when some object of class TA will be instantiated, constructor of Person
is called two times. Similarly, Destructor of Person will also be called two times. So all objects
of class TA have two copies of all members of Person, thus causing ambiguities.
The solution to this problem is the ‘virtual’ keyword. We make the classes ‘Faculty’ and ‘Student’
as virtual base classes to avoid two copies of ‘Person’ in ‘TA’ class.
Virtual inheritance means that there will be a single instance of the base A, regardless of how
many paths there are. This excellent S/O answer explains the reason behind this really well.
Deep Copy & Shallow Copy
In general, creating a copy of an object means to create an exact replica of the object having
the same literal value, data type, and resources. Depending upon the resources like dynamic
memory (allocated in heap) held by the object, either we need to perform Shallow Copy or Deep
Copy in order to create a replica of the object. In general, if the variables of an object have been
dynamically allocated then it is required to do a Deep Copy in order to create a copy of the
object.
Shallow Copy
In shallow copy, an object is created by simply copying the data of all variables of the original
object. C++ compiler implicitly creates a copy constructor and overloads the assignment
operator in order to perform shallow copy at compile time, as shown:
className O1 {
int a, b;
int *p;
};
//Copy Constructor
ClassName O2 = O1;
//Assignment Operator Overloading
ClassName O3;
O3 = O1;
This works well if none of the variables of the object are defined in the heap section of memory.
If some variables are dynamically allocated memory from the heap section, then the copied
object variable will also reference the same memory location which will create ambiguity and
run-time errors, since change made by one will be reflected in all other objects that copied the
data item from it as well. These problems can be avoided if we explicitly implement Deep Copy.
Deep Copy
In Deep copy, an object is created by copying data of all variables and it also allocates similar
memory resources with the same value to the object. In order to perform Deep copy, we need to
explicitly define the copy constructor and assign dynamic memory as well if required. Also, it is
required to dynamically allocate memory to the variables in the other constructors, as well.
className(className &obj) {
a = obj.a; b = obj.b;
p = new int;
*p = *(obj.p); //Deep Copy
}
Exception Handling
Exceptions are run-time anomalies or abnormal conditions that a program encounters during its
execution. There are two types of exceptions: a) Synchronous, b) Asynchronous (which are
beyond the program’s control, e.g, disc failure etc). C++ provides the following specialized
keywords for this purpose.
● try: represents a block of code that can throw an exception.
● throw: Used to throw an exception.
● catch: represents a block of code that is executed when a particular exception is thrown.
try and catch blocks work together: we use the try block to test some code, and if the exception
condition is satisfied, we throw the value that caused that error. The catch block then catches
that error(by taking it as an argument) and executes the catch block pertaining to that argument.
As a consequence, If no error occurs, the catch block is skipped.
try {
if (x < 0) {
throw x;
}
}
catch (int x) {
cout << "Exception Caught \n";
}
Important points about Exception Handling
There is a special catch block called catch all catch(...) that can be used to catch all types of
exceptions. An example is shown below, where since there is no catch block pertaining to int
specifically, the catch all block is executed.
int main()
{
try {
throw 10;
}
catch (char *excp) {
cout << "Caught " << excp;
}
catch (...) {
cout << "Default Exception\n";
}
return 0;
}
One more thing to note is that Implicit type conversion doesn’t happen for primitive types.
Also, If an exception is thrown and not caught anywhere, the program terminates abnormally.