Unit 4: Operator Overloading, Type Conversion and Inheritance - Subjective Questions
CSE202 — Object Oriented Programming • Practice Questions with Detailed Answers
20 questions
Define operator overloading. Explain its purpose and state the important rules that govern operator overloading in C++.
Operator overloading is a compile-time polymorphism feature that gives an existing C++ operator an additional meaning when it is applied to objects of a user-defined class.
For example, the + operator can be overloaded to add two Complex objects.
Purpose:
- It allows class objects to be manipulated using familiar operator notation.
- It makes expressions involving objects concise and readable.
- It enables user-defined types to behave similarly to built-in types.
Important rules:
- Only existing C++ operators can be overloaded; new operator symbols cannot be created.
- At least one operand must be a user-defined type.
- Overloading cannot change an operator's precedence, associativity, or number of operands.
- The original meaning of an operator for built-in types remains unchanged.
- Operators such as
::,.,.*,?:, andsizeofcannot be overloaded. - Operator functions may be implemented as member functions or non-member functions.
- The operators
=,[],(), and->must be overloaded as member functions.
Explain unary operator overloading in C++. Illustrate how the unary minus operator can be overloaded using a member function.
A unary operator operates on one operand. Examples include unary minus -, increment ++, decrement --, and logical NOT !.
When a unary operator is overloaded as a member function, it takes no explicit argument because the invoking object acts as the operand.
Example:
class Number {
int value;
public:
Number(int v = 0) : value(v) {}
Number operator-() const {
return Number(-value);
}
int getValue() const {
return value;
}
};If Number n(10); is declared, the expression Number result = -n; is internally interpreted as n.operator-().
Key points:
- The original object is not modified in this implementation.
- A new object containing the negated value is returned.
- The
constqualifier indicates that the operator function does not modify the invoking object.
Distinguish between prefix and postfix increment operator overloading. Write suitable C++ implementations for both forms.
Both forms use the ++ operator, but they differ in when the incremented value becomes visible.
Prefix increment:
- Syntax:
++obj - The object is incremented first, and the updated object is returned.
- It is overloaded without a dummy parameter.
Postfix increment:
- Syntax:
obj++ - The old value is returned, and then the object is incremented.
- It is distinguished by an unused
intparameter.
class Counter {
int value;
public:
Counter(int v = 0) : value(v) {}
Counter& operator++() {
++value;
return *this;
}
Counter operator++(int) {
Counter old = *this;
++value;
return old;
}
};Comparison:
- Prefix usually returns a reference to the modified object.
- Postfix returns a copy representing the object's previous state.
- Prefix is generally more efficient because it need not preserve the old value.
Describe binary operator overloading. Show how the + operator can be overloaded to add two complex numbers.
A binary operator operates on two operands. Examples include +, -, *, /, ==, and <.
When a binary operator is overloaded as a member function, the left operand is the invoking object and the right operand is passed as an argument.
class Complex {
double real;
double imag;
public:
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
Complex operator+(const Complex& other) const {
return Complex(real + other.real, imag + other.imag);
}
};For objects c1 and c2, the statement Complex c3 = c1 + c2; is equivalent to Complex c3 = c1.operator+(c2);.
If and , their sum is:
The function returns a new object, leaving both operands unchanged.
Compare overloading a binary operator using a member function and a friend function. When is a friend function preferable?
Member-function form:
Result ClassName::operator+(const ClassName& rhs) const;- The left operand invokes the function through
this. - Only the right operand appears as an explicit parameter.
- The left operand must be an object of the class containing the operator function.
Friend-function form:
friend Result operator+(const ClassName& lhs, const ClassName& rhs);- Both operands are explicit parameters.
- The function is not a class member, but it may access private and protected members when declared as a friend.
- It permits symmetric conversions for both operands.
A friend function is preferable when:
- The left operand is not an object of the class, as in
int + Object. - Equal treatment of both operands is required.
- The operator needs access to private data but should not conceptually be a member.
- Stream operators
<<and>>are overloaded because the left operand is normallystd::ostreamorstd::istream.
Operators such as =, [], (), and -> cannot use this approach because they must be member functions.
Explain the conversion of a basic type to a class type using a conversion constructor. Give a suitable example.
A basic value can be converted to a class object through a conversion constructor. This is usually a single-parameter constructor whose parameter has the source basic type.
class Distance {
double meters;
public:
Distance(double m) : meters(m) {}
double getMeters() const {
return meters;
}
};The initialization Distance d = 25.5; allows the compiler to construct a Distance object from the double value 25.5.
Important points:
- The constructor defines how the source value is represented in the class.
- A constructor with additional parameters can also be a conversion constructor if those parameters have default values.
- Implicit conversion can make expressions convenient but may also cause unintended conversions.
- Writing
explicit Distance(double m)prevents implicit conversion. - With an
explicitconstructor, direct initialization such asDistance d(25.5);is required.
Explain the conversion of a class type to a basic type using a conversion function. State the syntax and restrictions of such a function.
A class object can be converted to a basic type using a conversion function, also called a conversion operator.
class Distance {
double meters;
public:
Distance(double m = 0) : meters(m) {}
operator double() const {
return meters;
}
};For Distance d(12.5);, the statement double x = d; invokes d.operator double().
General syntax:
operator target_type() const;Restrictions and characteristics:
- A conversion function is a non-static member function.
- It has no declared return type because the target type appears in the function name.
- It normally takes no parameters.
- It can convert an object to a basic type or another class type.
- It should often be marked
constwhen it does not modify the object. - In modern C++, it may be declared
explicitto prevent unintended implicit conversions.
Define inheritance, base class, and derived class. Explain simple inheritance with a C++ example.
Inheritance is the mechanism by which a new class acquires data and behavior from an existing class and may add or modify features.
- A base class is the existing class whose members are inherited.
- A derived class is the new class that inherits from the base class.
- Simple inheritance occurs when one derived class inherits from exactly one base class.
class Person {
protected:
std::string name;
public:
void setName(const std::string& n) {
name = n;
}
};
class Student : public Person {
int rollNumber;
public:
void setRollNumber(int r) {
rollNumber = r;
}
};Here, Person is the base class and Student is the derived class. A Student object contains the inherited name state and the additional rollNumber state.
Benefits:
- Code reuse
- Natural representation of an is-a relationship
- Easier extension of existing classes
- Support for runtime polymorphism when virtual functions are used
Describe multilevel inheritance. Explain the accessibility of inherited members through different levels with an example.
Multilevel inheritance forms an inheritance chain in which one class is derived from another derived class.
class Person {
protected:
std::string name;
};
class Employee : public Person {
protected:
int employeeId;
};
class Manager : public Employee {
public:
void assign(const std::string& n, int id) {
name = n;
employeeId = id;
}
};The inheritance chain is Person -> Employee -> Manager.
Accessibility:
- Public members of
Personremain public through public inheritance. - Protected members of
Personremain protected inEmployeeand are accessible insideManager. - Private members of
Personexist in the inherited object but cannot be accessed directly byEmployeeorManager. Managerreceives accessible members from both earlier levels.
Constructor execution proceeds from the highest base class to the most derived class: Person, then Employee, then Manager. Destructor execution occurs in the reverse order.
What is multiple inheritance? Explain its advantages and potential problems using a suitable class structure.
Multiple inheritance occurs when one derived class inherits directly from two or more base classes.
class Printer {
public:
void print() {}
};
class Scanner {
public:
void scan() {}
};
class MultifunctionDevice : public Printer, public Scanner {
public:
void copy() {
scan();
print();
}
};MultifunctionDevice inherits the capabilities of both Printer and Scanner.
Advantages:
- It combines behavior from multiple independent abstractions.
- It supports reuse where a class naturally has several roles.
- It can model interfaces or capability-based designs.
Potential problems:
- Two base classes may contain members with the same name, creating ambiguity.
- Diamond-shaped inheritance can create duplicate copies of a common base.
- Constructor and destructor order becomes more complex.
- Tight coupling may make maintenance difficult.
Ambiguities can be resolved with scope qualification, overriding, or virtual inheritance, depending on the cause.
Explain hierarchical inheritance and distinguish it from multilevel inheritance.
Hierarchical inheritance occurs when two or more derived classes inherit independently from the same base class.
class Shape {
public:
void setColor() {}
};
class Circle : public Shape {
public:
void drawCircle() {}
};
class Rectangle : public Shape {
public:
void drawRectangle() {}
};Both Circle and Rectangle inherit the common features of Shape, but neither is derived from the other.
Hierarchical inheritance versus multilevel inheritance:
- Hierarchical inheritance has one base class with multiple direct derived classes.
- Multilevel inheritance forms a chain such as
A -> B -> C. - Hierarchical inheritance supports specialization into separate branches.
- Multilevel inheritance supports specialization through successive levels.
- In hierarchical inheritance, each derived object normally contains its own base-class subobject.
This model is useful when several related classes share common state or behavior but require different specialized operations.
Compare public, protected, and private inheritance in C++. Explain how each mode affects the accessibility of base-class members.
The inheritance mode controls how accessible base-class members appear inside the derived class and to users of the derived object.
| Base member | Public inheritance | Protected inheritance | Private inheritance |
|---|---|---|---|
public |
Becomes public |
Becomes protected |
Becomes private |
protected |
Remains protected |
Remains protected |
Becomes private |
private |
Not directly accessible | Not directly accessible | Not directly accessible |
Public inheritance:
- Models an is-a relationship.
- Public base operations remain part of the derived class's public interface.
- A derived object can be implicitly converted to a base object reference or pointer by ordinary client code.
Protected inheritance:
- Public and protected base members become protected.
- They are available to the derived class and its descendants but not to external users through the derived object.
Private inheritance:
- Public and protected base members become private in the derived class.
- Further derived classes cannot directly access those inherited members.
- It expresses implementation reuse more than subtype substitutability.
Base-class private members still exist in the object and can be accessed indirectly through accessible base-class functions.
Explain member-function overriding in inheritance. How does it differ from function overloading and function hiding?
Function overriding occurs when a derived class supplies a new implementation of a base-class virtual function with a matching signature.
class Shape {
public:
virtual double area() const {
return 0;
}
virtual ~Shape() = default;
};
class Rectangle : public Shape {
double width;
double height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() const override {
return width * height;
}
};When area() is called through a Shape pointer or reference that refers to a Rectangle, the derived implementation executes.
Differences:
- Overriding occurs across an inheritance relationship and normally requires a virtual base function for runtime dispatch.
- Overloading defines multiple functions with the same name but different parameter lists, usually in the same scope.
- Function hiding occurs when a derived-class declaration with the same name hides base-class overloads, even if the signatures differ.
The override specifier is recommended because the compiler verifies that a genuine override occurs. Hidden base overloads can be reintroduced with using Base::functionName;.
Describe the order of execution of constructors and destructors in single, multilevel, and multiple inheritance.
General constructor order:
- Virtual base classes are constructed first.
- Direct non-virtual base classes are constructed in the order in which they appear in the class declaration.
- Data members are constructed in the order in which they are declared in the class.
- The derived-class constructor body executes last.
Destructor order:
Destruction occurs in the exact reverse order of construction.
Single inheritance:
For Derived : public Base, construction is Base -> Derived, while destruction is Derived -> Base.
Multilevel inheritance:
For A -> B -> C, construction is A -> B -> C, while destruction is C -> B -> A.
Multiple inheritance:
For class D : public B1, public B2, construction is B1 -> B2 -> D, while destruction is D -> B2 -> B1.
The order of base construction follows the base-specifier list, not the order used in the constructor's initializer list. Similarly, member construction follows declaration order. A polymorphic base class should normally have a virtual destructor so deleting through a base pointer destroys the complete derived object.
What causes ambiguity in multiple inheritance? Explain different methods of resolving ambiguous member references.
Ambiguity occurs when a derived class inherits two or more accessible members having the same name and the compiler cannot determine which member is intended.
class A {
public:
void show() {}
};
class B {
public:
void show() {}
};
class C : public A, public B {};For an object c of class C, the call c.show() is ambiguous.
Resolution methods:
- Use the scope-resolution operator:
c.A::show();orc.B::show();. - Define a
show()function inCand explicitly call the required base implementation. - Use a
usingdeclaration, such asusing A::show;, when one inherited version should be exposed. - Rename or redesign members when the two operations represent different concepts.
- Use virtual inheritance when ambiguity results from duplicate copies of a common base class in a diamond hierarchy.
Scope qualification resolves same-name members from unrelated bases, while virtual inheritance addresses duplication of a shared ancestor.
Explain the diamond problem in inheritance. How does a virtual base class solve it?
The diamond problem occurs when two intermediate classes inherit from the same base class and another class inherits from both intermediate classes.
class Person {
public:
std::string name;
};
class Student : virtual public Person {};
class Employee : virtual public Person {};
class TeachingAssistant : public Student, public Employee {};Without virtual inheritance, TeachingAssistant would contain two separate Person subobjects: one through Student and one through Employee. Consequently, an expression such as ta.name would be ambiguous.
Declaring Person as a virtual base class ensures that the most-derived object contains only one shared Person subobject.
Important consequences:
- Duplicate storage for the common base is removed.
- Access to common base members becomes unambiguous.
- The most-derived class is responsible for initializing the virtual base.
- A virtual base is constructed before non-virtual base classes.
- Virtual inheritance may introduce implementation overhead because the compiler must locate the shared base subobject.
Define aggregation and explain how it represents a weak whole-part relationship. Give an example.
Aggregation is a form of association in which one class contains or refers to objects of another class, but the contained objects can exist independently of the whole. It represents a weak has-a relationship.
class Teacher {
public:
void teach() {}
};
class Department {
Teacher* head;
public:
Department(Teacher* teacher) : head(teacher) {}
};Here, a Department refers to a Teacher, but the teacher is created and managed independently. Destroying the department does not necessarily destroy the teacher.
Characteristics:
- The part has an independent lifetime.
- The whole usually stores a pointer, reference, or non-owning handle to the part.
- A part may be shared by multiple aggregate objects.
- Ownership is external or explicitly managed elsewhere.
- Aggregation is suitable when the relationship can change during the lifetime of the whole.
In modern C++, non-owning raw pointers or references may express aggregation, but their lifetime requirements must be documented and enforced.
Define composition and explain how object lifetime and ownership are handled in a composition relationship.
Composition is a strong whole-part relationship in which the whole owns its component objects. The components normally do not have an independent lifetime relative to the whole.
class Engine {
public:
Engine() {}
void start() {}
};
class Car {
Engine engine;
public:
void start() {
engine.start();
}
};The Engine object is stored directly inside Car.
Lifetime and ownership:
- The component is constructed automatically when the whole object is constructed.
- The component is destroyed automatically when the whole is destroyed.
- The whole exclusively controls the component's lifetime.
- Construction of member objects occurs before the containing class's constructor body.
- Member objects are destroyed in reverse declaration order after the containing class's destructor body finishes.
Composition is often preferred over implementation inheritance when a class needs to use another class's behavior but does not satisfy a true is-a relationship.
Compare aggregation and composition with respect to ownership, lifetime, sharing, and implementation.
Both aggregation and composition represent has-a relationships, but they differ in the strength of ownership.
| Aspect | Aggregation | Composition |
|---|---|---|
| Relationship | Weak whole-part | Strong whole-part |
| Ownership | Whole generally does not own the part | Whole owns the part |
| Lifetime | Part can outlive the whole | Part's lifetime is tied to the whole |
| Sharing | Part may be shared | Part is normally exclusive |
| Typical representation | Pointer or reference | Direct data member or owning smart pointer |
| Destruction | Whole does not automatically destroy a non-owned part | Component is destroyed with the whole |
Aggregation example: A Team refers to Player objects that can exist independently or move to another team.
Composition example: A House contains Room objects whose existence is modeled as part of that house.
The choice should be based on ownership semantics rather than syntax alone. An owning std::unique_ptr can represent composition even though the component is dynamically allocated, while a raw pointer commonly represents non-owning aggregation.
Design and explain a C++ class hierarchy that demonstrates multiple inheritance, ambiguity resolution, virtual inheritance, overriding, and constructor order.
A suitable hierarchy uses a common Person base, two virtual intermediate bases, and one final derived class.
class Person {
public:
Person() {}
virtual void role() const {}
virtual ~Person() = default;
};
class Student : virtual public Person {
public:
Student() {}
void identify() const {}
};
class Employee : virtual public Person {
public:
Employee() {}
void identify() const {}
};
class TeachingAssistant : public Student, public Employee {
public:
TeachingAssistant() : Person(), Student(), Employee() {}
void role() const override {}
void identifyAsStudent() const {
Student::identify();
}
void identifyAsEmployee() const {
Employee::identify();
}
};Explanation:
TeachingAssistantdemonstrates multiple inheritance by inheriting fromStudentandEmployee.- Both intermediate classes virtually inherit
Person, so only onePersonsubobject exists. TeachingAssistant::role()overrides the virtual function declared inPerson.- The duplicate
identify()names are resolved explicitly withStudent::identify()andEmployee::identify(). - Construction order is
Person,Student,Employee, and finallyTeachingAssistant. - Destruction occurs in the reverse order.
- Because
Personis a virtual base, the most-derived classTeachingAssistantis responsible for initializing it.
This design combines reuse and polymorphism while avoiding duplicate common-base state.
Define operator overloading. Explain its purpose and state the important rules that govern operator overloading in C++.
Operator overloading is a compile-time polymorphism feature that gives an existing C++ operator an additional meaning when it is applied to objects of a user-defined class.
For example, the + operator can be overloaded to add two Complex objects.
Purpose:
- It allows class objects to be manipulated using familiar operator notation.
- It makes expressions involving objects concise and readable.
- It enables user-defined types to behave similarly to built-in types.
Important rules:
- Only existing C++ operators can be overloaded; new operator symbols cannot be created.
- At least one operand must be a user-defined type.
- Overloading cannot change an operator's precedence, associativity, or number of operands.
- The original meaning of an operator for built-in types remains unchanged.
- Operators such as
::,.,.*,?:, andsizeofcannot be overloaded. - Operator functions may be implemented as member functions or non-member functions.
- The operators
=,[],(), and->must be overloaded as member functions.
Did this save you a night before the exam?
LPU Notes is free, and it stays free. Ads cover part of the server bill. The rest comes out of a student's own pocket: the domain, the storage, and keeping the site up through the weeks everyone needs it at once.
The payment button didn't load. An ad blocker or a filtered network is the usual reason. to try again.
Nothing here is ever locked, and nothing unlocks. Chip in only if it was worth it. What it pays for →