Operator Overloading and Inheritance in C++
1. Increment Operator Overloading
- Pre-increment returns a reference, while post-increment returns a value.
// Custom integer class
class CustomInt {
friend std::ostream& operator<<(std::ostream& output, CustomInt value);
public:
CustomInt() : data(0) {}
// Pre-increment operator overloading
CustomInt& operator++() {
++data;
return *this;
}
// Post-increment operator overloading
// The int parameter is a placeholder to differentiate from pre-increment
CustomInt operator++(int) {
CustomInt temp = *this;
++data;
return temp;
}
private:
int data;
};
// Overload left shift operator
std::ostream& operator<<(std::ostream& output, CustomInt value) {
output << value.data;
return output;
}
void testPreIncrement() {
CustomInt num;
std::cout << ++num << std::endl;
}
void testPostIncrement() {
CustomInt num;
std::cout << num++ << std::endl;
std::cout << num << std::endl;
}
int main() {
testPostIncrement();
return 0;
}
2. Assignment Operator Overloading
C++ compiler automatically provides at least four functions for a class:
- Default constructor (no parameters, empty body)
- Default destructor (no parameters, empty body)
- Default copy constructor (copies property values)
- Assignment operator
operator=(performs value copy)
If a class has properties pointing to heap memory, assignment operations can lead to shallow vs. deep copy issues.
class Person {
public:
Person(int age) {
agePtr = new int(age);
}
~Person() {
if (agePtr != nullptr) {
delete agePtr;
agePtr = nullptr;
}
}
// Overload assignment operator
Person& operator=(Person& other) {
// Compiler provides shallow copy: agePtr = other.agePtr;
// Deep copy implementation
if (agePtr != nullptr) {
delete agePtr;
agePtr = nullptr;
}
agePtr = new int(*other.agePtr);
return *this;
}
int* agePtr;
};
void testAssignment() {
Person person1(18);
Person person2(20);
Person person3(40);
person3 = person2 = person1; // Chained assignment
std::cout << *person1.agePtr << std::endl;
std::cout << *person2.agePtr << std::endl;
std::cout << *person3.agePtr << std::endl;
}
3. Relational Operator Overloading
class Person {
public:
Person(std::string name, int age) : personName(name), personAge(age) {}
// Overload equality operator
bool operator==(Person& other) {
return (personAge == other.personAge && personName == other.personName);
}
// Overload inequality operator
bool operator!=(Person& other) {
return !(*this == other);
}
std::string personName;
int personAge;
};
void testComparison() {
Person person1("Tom", 18);
Person person2("Tom", 18);
if (person1 == person2) {
std::cout << "Equal" << std::endl;
} else {
std::cout << "Not equal" << std::endl;
}
}
4. Function Call Operator Overloading
- The function call operator
()can be overloaded. - When overloaded, it resembles a function call, hence called a functor.
- Functors are flexible with no fixed implementation pattern.
class Printer {
public:
// Overload function call operator
void operator()(std::string text) {
std::cout << text << std::endl;
}
};
void printFunction(std::string text) {
std::cout << text << std::endl;
}
void testPrinter() {
Printer printer;
printer("Hello"); // Functor usage
printFunction("Hello");
}
// Flexible functor example
class Adder {
public:
int operator()(int x, int y) {
return x + y;
}
};
void testAdder() {
Adder adder;
int result = adder(10, 20);
std::cout << result << std::endl;
// Anonymous functor object
std::cout << Adder()(10, 20) << std::endl;
}
5. Inheritance
- Benefits: Reduces code duplication.
- Public inheritance syntax:
class Derived : public Base - Derived class inherits common features from base class while adding specific ones.
// Base class with common interface
class BasePage {
public:
void header() {
std::cout << "Home, Courses, Login, Register (Common Header)" << std::endl;
}
void footer() {
std::cout << "Help Center, Collaboration, Site Map (Common Footer)" << std::endl;
}
void sidebar() {
std::cout << "Java, Python, C++ (Common Category List)" << std::endl;
}
};
class PythonPage : public BasePage {
public:
void content() {
std::cout << "Python Course Videos" << std::endl;
}
};
void testInheritance() {
std::cout << "Python Download Page:" << std::endl;
PythonPage page;
page.header();
page.footer();
page.sidebar();
page.content();
}
6. Inheritance Types
Public Inheritance
class BaseClass1 {
public:
int publicMember;
protected:
int protectedMember;
private:
int privateMember;
};
class Derived1 : public BaseClass1 {
public:
void accessMembers() {
publicMember = 10; // Public in derived class
protectedMember = 20; // Protected in derived class
// privateMember = 10; // Error: inaccessible
}
};
void testPublicInheritance() {
Derived1 obj;
obj.publicMember = 10;
// obj.protectedMember = 10; // Error: protected
// obj.privateMember = 10; // Error: private
}
Protected Inheritance
class BaseClass2 {
public:
int publicMember;
protected:
int protectedMember;
private:
int privateMember;
};
class Derived2 : protected BaseClass2 {
public:
void accessMembers() {
publicMember = 100; // Becomes protected in derived class
protectedMember = 100; // Remains protected
// privateMember = 100; // Error: inaccessible
}
};
void testProtectedInheritance() {
Derived2 obj;
// obj.publicMember = 100; // Error: protected
// obj.protectedMember = 100; // Error: protected
// obj.privateMember = 100; // Error: private
}
Private Inheritance
class BaseClass3 {
public:
int publicMember;
protected:
int protectedMember;
private:
int privateMember;
};
class Derived3 : private BaseClass3 {
public:
void accessMembers() {
publicMember = 100; // Becomes private in derived class
protectedMember = 100; // Becomes private
// privateMember = 100; // Error: inaccessible
}
};
void testPrivateInheritance() {
Derived3 obj;
// obj.publicMember = 100; // Error: private
// obj.protectedMember = 100; // Error: private
// obj.privateMember = 100; // Error: private
}
7. Object Model in Inheritance
Use developer command prompt tools to inspect object models:
- Change drive:
F: - Navigate path:
cd path - View layout:
cl /d1 reportSingleClassLayoutClassName filename
// Object model in inheritance
class BaseClass {
public:
int publicMember;
protected:
int protectedMember;
private:
int privateMember;
};
class DerivedClass : public BaseClass {
public:
int derivedMember;
};
void testObjectModel() {
DerivedClass obj;
// All non-static members from base class are inherited
// Private members are hidden by compiler but still inherited
std::cout << sizeof(obj) << std::endl; // Output: 16
}