Pic Unit 2 and 3
Pic Unit 2 and 3
A virtual base class ensures that only one copy of the base
class's members is inherited, regardless of the inheritance
paths.
class A { /* Base Class */ };
class B : virtual public A { /*
Intermediate Class */ };
class C : virtual public A { /*
Intermediate Class */ };
class D : public B, public C { /*
Derived Class */ };
Object Slicing
Answer:
Constructors and destructors are fundamental to object-oriented programming, as they manage
the lifecycle of objects.
Dynamic Constructors:
Dynamic constructors involve the allocation of memory at runtime using new. These constructors
are beneficial when the size or nature of the object depends on runtime inputs. For instance, a
class managing a dynamic array can use a dynamic constructor to allocate memory based on user
input.
Explicit Constructors:
Explicit constructors prevent implicit type conversions, ensuring stricter type safety. For
example, without marking a constructor as explicit, an integer value might implicitly convert
to an object, leading to unexpected behavior. Explicit constructors require an explicit call, which
improves code clarity and prevents unintended conversions.
Destructors:
Destructors are defined using a tilde (~) followed by the class name. They release resources and
perform cleanup when the object is destroyed. For instance, if a class allocates memory
dynamically, the destructor should free this memory to prevent leaks. Unlike constructors,
destructors cannot have parameters or be overloaded, ensuring predictable behavior.
In summary, constructors initialize objects, and destructors clean them up. Dynamic constructors
allow flexible resource allocation, while explicit constructors enforce stricter type control.
Together, they ensure efficient resource management and object lifecycle handling in object-
oriented programming.
Question 2: Explain operator overloading and the rules for overloading
operators. Discuss with examples how operators like + and [] can be
overloaded. (5 Marks)
Answer:
Operator Overloading:
Operator overloading allows customizing the behavior of operators (+, -, [], etc.) for user-
defined types (e.g., classes). It makes code intuitive by enabling operators to work seamlessly
with objects as they do with basic types. For example, the + operator can be overloaded to add
two complex numbers.
1. Only Certain Operators Can Be Overloaded: Some operators (e.g., ::, sizeof, .*)
cannot be overloaded.
2. Maintain Logical Consistency: Overloaded operators should mimic the expected
behavior. For example, if + is overloaded, it should perform addition-like
functionality.
3. At Least One Operand Must Be a User-Defined Type: This ensures meaningful
customization of the operator for specific data types.
4. Cannot Change Operator Precedence or Associativity: Overloading does not alter
how operators are evaluated in expressions.
5. Return Type and Parameters: Overloaded operators are implemented as member
or friend functions. They can take arguments and return values based on the desired
functionality.
Overloading +:
The + operator can be overloaded for a class Complex to add two complex
numbers.
class Complex {
int real, imag;
public:
Complex(int r = 0, int i = 0) : real(r), imag(i) {}
Complex operator+(const Complex& c) {
return Complex(real + c.real, imag + c.imag);
}
};
Overloading []:
The [] operator can be overloaded to access elements in a class managing an
array.
class Array {
int* arr;
int size;
public:
Array(int s) : size(s) { arr = new int[s]; }
int& operator[](int index) { return arr[index]; }
~Array() { delete[] arr; }
};
Summary:
Operator overloading enhances code readability and usability by extending
operators to user-defined types. By following rules and implementing them
logically, operators like + and [] can perform custom tasks on objects,
making code more intuitive.
Question 3: Explain type conversion in C++ and describe how to convert basic
types to class types, class types to basic types, and one class type to another.
Provide examples. (5 Marks)
Answer:
Type Conversion in C++:
Type conversion allows converting data between different types. In object-oriented
programming, this includes conversions between basic types (e.g., int, float) and user-defined
class types, as well as between different class types.
class Distance {
int meters;
public:
Distance(int m) : meters(m) {} // Constructor for conversion
void display() { std::cout << meters << " meters"; }
};
Distance d = 10; // Converts integer to Distance object
class Distance {
int meters;
public:
Distance(int m) : meters(m) {}
explicit operator int() { return meters; } // Conversion to int
};
Distance d(15);
int meters = (int)d; // Converts Distance to integer
class Distance {
int meters;
public:
Distance(int m) : meters(m) {}
int getMeters() { return meters; }
};
class Length {
int centimeters;
public:
Length(Distance d) : centimeters(d.getMeters() * 100) {} // Conversion
constructor
};
Distance d(5);
Length l = d; // Converts Distance to Length
Summary:
Type conversion enhances interoperability between types. Converting basic types to class types
simplifies object creation, converting class types to basic types provides data in primitive forms,
and converting between class types allows seamless transitions between related objects.
Question 4: Define virtual functions and explain the concept of early binding,
late binding, pure virtual functions, abstract classes, and virtual destructors
with examples. (5 Marks)
Answer:
Virtual Functions:
Virtual functions enable polymorphism by allowing derived classes to override base class
methods dynamically. They are declared using the virtual keyword in the base class. When a
derived class object is accessed through a base class pointer, the overridden function is called
dynamically, based on the actual object type.
class Base {
public:
virtual void display() { std::cout << "Base class"; }
};
class Derived : public Base {
public:
void display() override { std::cout << "Derived class"; }
};
Base* obj = new Derived();
obj->display(); // Calls Derived class function
Early Binding:
In early binding (compile-time binding), the function to be called is determined at compile time,
based on the pointer or reference type.
Late Binding:
In late binding (runtime binding), the function call is resolved at runtime, based on the actual
type of the object. Virtual functions enable late binding.
class Abstract {
public:
virtual void func() = 0; // Pure virtual function
};
class Concrete : public Abstract {
public:
void func() override { std::cout << "Implemented in derived"; }
};
Virtual Destructors:
Virtual destructors ensure proper cleanup of derived objects when deleted through a base class
pointer. Without them, only the base class destructor is called, leading to resource leaks.
class Base {
public:
virtual ~Base() { std::cout << "Base destructor"; }
};
class Derived : public Base {
public:
~Derived() { std::cout << "Derived destructor"; }
};
Base* obj = new Derived();
delete obj; // Calls both destructors
Summary:
Virtual functions, along with late binding, enable polymorphism. Abstract classes provide a
blueprint for derived classes, and virtual destructors ensure complete cleanup. Together, they
form the foundation of runtime polymorphism in C++.