10VirtualFunctionsUnitV
10VirtualFunctionsUnitV
class Animal {
public:
virtual void sound() { // Virtual function in base class
cout << "Animal sound" << endl;
}
};
int main() {
Animal* animalPtr;
Dog dog;
Cat cat;
animalPtr = &dog;
animalPtr->sound(); // Will call Dog's sound() due to late binding
animalPtr = &cat;
animalPtr->sound(); // Will call Cat's sound() due to late binding
return 0;
}
Explanation:
• Animal has a virtual function sound().
• Dog and Cat override this function with their own versions.
• In the main() function, animalPtr is a base class pointer (Animal*), but it points to
derived class objects (Dog and Cat). The call to sound() is resolved at runtime based
on the type of the object being pointed to (Dog or Cat). This is an example of
dynamic binding (or late binding).
• The correct version of sound() is called based on the actual type of the object (Dog or
Cat), not the type of the pointer.
class Shape {
public:
virtual void draw() = 0; // Pure virtual function (abstract class)
};
int main() {
Shape* shapePtr;
Circle circle;
Rectangle rectangle;
shapePtr = &circle;
shapePtr->draw(); // Calls Circle's draw()
shapePtr = &rectangle;
shapePtr->draw(); // Calls Rectangle's draw()
return 0;
}
Explanation:
• Shape is an abstract class because it has a pure virtual function draw() (= 0).
• Both Circle and Rectangle inherit from Shape and implement the draw() function.
• In main(), shapePtr is a pointer to Shape, but it points to instances of Circle and
Rectangle. When shapePtr->draw() is called, C++ will dynamically resolve the call to
the appropriate draw() method based on the actual type of the object (Circle or
Rectangle).
• This example demonstrates polymorphism where the same method draw() behaves
differently based on the object type at runtime.
int main() {
Base* basePtr = new Derived();
delete basePtr; // Destructor of both Base and Derived will be called
return 0;
}
Explanation:
• Base class has a virtual destructor. This is important for proper destruction of
derived class objects when they are deleted via a base class pointer.
• If the destructor in the base class is not virtual, only the base class destructor will be
called when an object is deleted using a base class pointer (delete basePtr), leading to
resource leaks or incomplete cleanup.
• Since Base's destructor is virtual, both the Derived class's destructor and the Base
class's destructor are called when the object is deleted, ensuring proper cleanup of
resources.
class Animal {
public:
virtual void speak() { cout << "Animal speaks" << endl; } // Virtual function
};
int main() {
Animal* animalPtr;
Dog dog;
Cat cat;
animalPtr = &dog;
animalPtr->speak(); // Calls Dog's speak()
animalPtr = &cat;
animalPtr->speak(); // Calls Cat's speak()
return 0;
}
Explanation:
• speak() is a virtual function in the Animal class.
• Dog and Cat override the speak() function to provide their own behavior.
• The function calls to animalPtr->speak() invoke the overridden speak() method based
on the actual type of object (Dog or Cat) that animalPtr points to.
Bindings
In C++, binding refers to the linking of a function call to the actual code that will be
executed. Binding can happen at compile time (known as early binding) or at runtime
(known as late binding). Here’s a breakdown of these concepts along with examples.
class Animal {
public:
void sound() { // Regular function, not virtual
cout << "Animal sound" << endl;
}
};
int main() {
Animal animal;
Dog dog;
return 0;
}
Explanation:
• Here, sound() is a regular function in both Animal and Dog.
• Since sound() is not virtual, the call to sound() is determined based on the type of the
pointer, not the object it points to.
• Therefore, ptr->sound() calls Animal::sound(), even though ptr points to a Dog object.
This is an example of early binding.
int main() {
Animal* animalPtr;
Dog dog;
animalPtr = &dog;
animalPtr->sound(); // Calls Dog's sound() due to late binding
return 0;
}
Explanation:
• Here, sound() is a virtual function in Animal, allowing it to be overridden in Dog.
• When animalPtr->sound() is called, it calls the overridden Dog::sound() function
because of late binding.
• C++ determines at runtime that animalPtr points to a Dog object, so it calls
Dog::sound() instead of Animal::sound().
Virtual Functions
A virtual function is a function in a base class that you expect to override in derived classes.
It enables late binding and allows for polymorphic behavior. Virtual functions are typically
used to ensure that the correct function in a derived class is called when using base class
pointers or references.
Example of Virtual Function:
cpp
Copy code
#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() { // Virtual function
cout << "Drawing shape" << endl;
}
};
int main() {
Shape* shapePtr;
Circle circle;
shapePtr = &circle;
shapePtr->draw(); // Calls Circle's draw() due to virtual function
return 0;
}
Explanation:
• The draw() function in Shape is a virtual function.
• Circle overrides draw() with its own implementation.
• When shapePtr->draw() is called, it calls the overridden version in Circle because of
the virtual function, even though shapePtr is a pointer to Shape.
A pure virtual function is a virtual function that has no implementation in the base class and
is assigned 0. A class with one or more pure virtual functions is called an abstract class.
Abstract classes cannot be instantiated directly; they are designed to act as a base class for
other classes.
Pure Virtual Function and Abstract Class:
#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() = 0; // Pure virtual function, makes Shape an abstract class
};
int main() {
Shape* shapePtr;
Circle circle;
Square square;
shapePtr = &circle;
shapePtr->draw(); // Calls Circle's draw()
shapePtr = □
shapePtr->draw(); // Calls Square's draw()
return 0;
}
Explanation:
• Shape has a pure virtual function draw(), making Shape an abstract class.
• Circle and Square inherit from Shape and override draw() with specific
implementations.
• Shape* shapePtr can point to either Circle or Square objects, and the correct draw()
function is called at runtime due to late binding enabled by the pure virtual function.
try {
// Code that may throw an exception
throw exception; // Throws an exception
}
catch (type_of_exception) {
// Code to handle the exception
}
Example 1: Basic Exception Handling with Division by Zero
A simple example where we try to perform division and catch a division-by-zero error.
#include <iostream>
using namespace std;
int main() {
int numerator = 10;
int denominator = 0;
try {
if (denominator == 0)
throw "Division by zero error"; // Throwing an exception
cout << "Result: " << numerator / denominator << endl;
}
catch (const char* e) { // Catching the exception
cout << "Error: " << e << endl;
}
return 0;
}
Explanation:
• If denominator is zero, the throw statement is executed, throwing a string as an
exception.
• The catch block catches this exception and displays an error message.
• This prevents the program from crashing and instead displays a user-friendly error
message.
int main() {
try {
int choice;
cout << "Enter 1 to throw integer, 2 to throw double, 3 to throw string: ";
cin >> choice;
if (choice == 1)
throw 100; // Throw an integer
else if (choice == 2)
throw 99.99; // Throw a double
else if (choice == 3)
throw "Exception string"; // Throw a string
}
catch (int e) {
cout << "Caught an integer: " << e << endl;
}
catch (double e) {
cout << "Caught a double: " << e << endl;
}
catch (const char* e) {
cout << "Caught a string: " << e << endl;
}
return 0;
}
Explanation:
• Depending on the user's input, an exception of a specific type is thrown.
• Different catch blocks handle each exception type separately.
• The correct catch block is selected based on the type of the exception thrown,
showcasing the flexibility of C++ exception handling.
int main() {
try {
throw 404; // Throwing an integer exception
}
catch (int e) {
cout << "Caught an integer: " << e << endl;
}
catch (...) { // Catch-all handler
cout << "Caught an unknown exception" << endl;
}
return 0;
}
Explanation:
• The catch(...) block catches any type of exception, regardless of its type.
• If an exception is thrown that doesn’t match any previous catch block, the catch-all
handler will catch it.
SOMESH KUMAR DEWANGAN DEPARTMENT OF CSE 15
SHRI SHANKARACHARYA TECHNICAL CAMPUS
void functionA() {
try {
throw "Error occurred in functionA"; // Throw an exception
}
catch (const char* e) {
cout << "Caught in functionA: " << e << endl;
throw; // Rethrow the exception to main()
}
}
int main() {
try {
functionA();
}
catch (const char* e) {
cout << "Caught in main: " << e << endl;
}
return 0;
}
Explanation:
• functionA() throws an exception and catches it, then re-throws it.
• In main(), the re-thrown exception is caught again, demonstrating that exceptions can
propagate through multiple levels if necessary.
class DivideByZeroException {
public:
string message;
DivideByZeroException(string msg) : message(msg) {}
};
int main() {
try {
cout << "Result: " << divide(10, 0) << endl;
}
catch (DivideByZeroException& e) { // Catch the custom exception
cout << "Caught an exception: " << e.message << endl;
}
return 0;
}
Explanation:
• DivideByZeroException is a custom exception class with a message property.
• divide() throws a DivideByZeroException if the denominator is zero.
• In main(), we catch this specific exception type and print the error message, making
the exception handling more meaningful and informative.
Specifying Exceptions (Deprecated in C++ 11 and Removed in C++ 17)
In older C++ standards, you could specify the types of exceptions a function could throw
using the throw() keyword, such as void func() throw(int, double);. However, this
approach has been deprecated and is no longer used in modern C++.
In C++11 and later, the recommended way is to avoid specifying exception types and use
exception-safe practices. Instead, the noexcept keyword indicates that a function does not
throw exceptions, which can optimize performance.
Example (Deprecated):
// Not recommended in C++11 and later
void func() throw(int, double) {
// Function that may throw int or double exceptions
}
Using noexcept:
#include <iostream>
using namespace std;
int main() {
safeFunction();
return 0;
}
Explanation:
1. Template Concepts
which are a mechanism to impose constraints on template parameters. Concepts enhance
templates by specifying requirements that types must satisfy to be used with the template.
template <typename T>
concept ConceptName = <constraint_expression>;
int main() {
std::cout << add(3, 5) << '\n'; // Valid
SOMESH KUMAR DEWANGAN DEPARTMENT OF CSE 19
SHRI SHANKARACHARYA TECHNICAL CAMPUS
int main() {
std::cout << findMax(10, 20) << '\n'; // Calls with int
std::cout << findMax(10.5, 7.8) << '\n'; // Calls with double
}
Key Points:
• The typename keyword can be replaced with class.
• Function templates are instantiated at compile time when called with specific types.
3. Class Templates
Class templates allow the creation of generic classes where the data type can be specified
when an object is instantiated.
Syntax
template <typename T>
class ClassName {
// Members and methods
};
Example
#include <iostream>
template <typename T>
class Box {
T value;
public:
Box(T val) : value(val) {}
void display() {
std::cout << "Value: " << value << '\n';
}
};
int main() {
Box<int> intBox(10); // Integer specialization
Box<double> doubleBox(3.14); // Double specialization
intBox.display();
doubleBox.display();
}
• The class template is instantiated when an object is created with a specific type.
• Class templates can have multiple type parameters: template <typename T1,
typename T2>.
Files:
File handling in C++ involves working with files using input/output streams to perform
operations like reading, writing, and updating data. Here's a detailed explanation of key
concepts in file handling:
1. File Streams
File streams are objects in C++ used to handle file input and output operations. The file
stream classes in the <fstream> header provide mechanisms for file handling. The primary
file stream classes are:
• ifstream: For input (reading from files).
• ofstream: For output (writing to files).
• fstream: For both input and output.
Basic Syntax
#include <fstream>
// Creating file stream objects
std::ifstream inputFile; // Input stream
std::ofstream outputFile; // Output stream
std::fstream file; // Input/Output stream
int main() {
std::ifstream file("example.txt");
if (!file) {
std::cerr << "Error: File could not be opened.\n";
return 1;
}
Writing to a File
#include <iostream>
#include <fstream>
int main() {
std::ofstream outFile("output.txt");
if (!outFile) {
std::cerr << "Error opening file for writing.\n";
return 1;
}
outFile << "Hello, World!\n";
outFile << "Writing to a file in C++.\n";
outFile.close();
return 0;
}
Reading from a File
#include <iostream>
#include <fstream>
#include <string>
int main() {
std::ifstream inFile("output.txt");
std::string line;
if (!inFile) {
std::cerr << "Error opening file for reading.\n";
return 1;
}
while (std::getline(inFile, line)) {
std::cout << line << '\n';
}
inFile.close();
return 0;
6. Updating Files
Updating files involves modifying the content of an existing file. You can read data, modify it
in memory, and then write it back.