Unit 5: Dynamic Memory Management and Polymorphism - Subjective Questions
CSE202 — Object Oriented Programming • Practice Questions with Detailed Answers
20 questions
Define dynamic memory allocation. Explain the use of the new and delete operators in C++ with an example.
Dynamic memory allocation is the process of allocating memory during program execution. The memory is obtained from the free store (heap) and remains allocated until it is explicitly released.
- The
newoperator allocates memory and returns a pointer to it. - The
deleteoperator releases memory allocated for a single object. - Memory should be released when it is no longer needed.
int* ptr = new int;
*ptr = 25;
cout << *ptr;
delete ptr;
ptr = nullptr;Here, new int allocates memory for an integer. The statement delete ptr releases that memory, while assigning nullptr prevents accidental use of a dangling pointer.
Describe how arrays are dynamically allocated and deallocated in C++. Why must delete[] be used instead of delete for a dynamically allocated array?
A dynamic array is created when its size is determined at run time.
int n;
cin >> n;
int* values = new int[n];
for (int i = 0; i < n; ++i) {
values[i] = i * 10;
}
delete[] values;
values = nullptr;Important points:
new int[n]allocates storage for integer objects.delete[] valuesreleases the complete array.deleteis intended for a single dynamically allocated object.delete[]ensures that the destructor of every array element is called when the elements are class objects.- Using
deletefor memory allocated withnew[]results in undefined behavior.
Therefore, allocation and deallocation forms must match: new with delete, and new[] with delete[].
What is a dynamic constructor? Explain how a constructor can allocate memory dynamically, with a suitable C++ example.
A dynamic constructor is a constructor that allocates memory dynamically while initializing an object. It is useful when the amount of memory required by an object is known only at run time.
class IntegerArray {
private:
int* data;
int size;
public:
IntegerArray(int n) : size(n) {
data = new int[size]{};
}
~IntegerArray() {
delete[] data;
}
};In this example:
- The constructor receives the required array size.
new int[size]{}dynamically allocates and initializes the array.- The pointer is stored as an object data member.
- The destructor releases the dynamically allocated array.
A class that owns dynamic memory should also define or appropriately control copying and assignment so that objects do not accidentally share and delete the same memory.
Explain allocation failure in C++. Compare the throwing and non-throwing forms of the new operator.
An allocation failure occurs when the requested dynamic memory cannot be provided, for example because the available memory is insufficient.
By default, new throws a std::bad_alloc exception on failure:
try {
int* data = new int[1000000];
delete[] data;
} catch (const std::bad_alloc& error) {
cerr << "Allocation failed";
}The non-throwing form uses std::nothrow and returns nullptr on failure:
int* data = new (std::nothrow) int[1000000];
if (data == nullptr) {
cerr << "Allocation failed";
} else {
delete[] data;
}Comparison:
- Ordinary
new: throwsstd::bad_alloc. new (std::nothrow): returnsnullptr.- Exception-based handling separates failure handling from normal logic.
- The non-throwing form requires an explicit null-pointer test before dereferencing the pointer.
Define a memory leak. Discuss its causes, effects, detection, and prevention in object-oriented C++ programs.
A memory leak occurs when dynamically allocated memory is no longer accessible but has not been released. Such memory remains unavailable for reuse until the program terminates.
Common causes:
- Forgetting to call
deleteordelete[]. - Overwriting the only pointer to an allocated block.
- Returning early or throwing an exception before deallocation.
- Incorrect ownership design.
- Failing to release resources in a destructor.
int* ptr = new int(10);
ptr = new int(20); // The first allocation is leakedEffects:
- Increasing memory consumption.
- Reduced performance.
- Allocation failures in long-running programs.
- Possible application or system instability.
Prevention and detection:
- Follow RAII: acquire resources in constructors and release them in destructors.
- Prefer
std::vector,std::string,std::unique_ptr, andstd::shared_ptr. - Clearly define object ownership.
- Use tools such as sanitizers, Valgrind, or IDE memory profilers.
- Ensure every successful
newhas one matchingdeletewhen raw ownership is unavoidable.
Distinguish between compile-time polymorphism and run-time polymorphism in C++.
Polymorphism means that one interface can represent different forms of behavior.
| Basis | Compile-time polymorphism | Run-time polymorphism |
|---|---|---|
| Resolution | Performed by the compiler | Performed during execution |
| Binding | Early or static binding | Late or dynamic binding |
| Main mechanisms | Function overloading, operator overloading, templates | Function overriding through virtual functions |
| Inheritance | Usually not required | Generally requires inheritance |
| Performance | Usually faster because dispatch is known early | Has a small virtual-dispatch overhead |
| Flexibility | Behavior is fixed by static types | Behavior depends on the actual object type |
Example:
- Calling one of several overloaded
print()functions demonstrates compile-time polymorphism. - Calling an overridden virtual
draw()function through a base pointer demonstrates run-time polymorphism.
Compile-time polymorphism is appropriate when types are known during compilation, whereas run-time polymorphism is useful for extensible class hierarchies.
Explain how function overloading and operator overloading implement compile-time polymorphism. State the important restrictions on overloading.
Function overloading allows multiple functions to have the same name but different parameter lists. The compiler selects the best matching function from the argument types and number of arguments.
void display(int value);
void display(double value);
void display(const char* value);Operator overloading gives an existing operator a class-specific meaning.
class Number {
public:
int value;
Number operator+(const Number& other) const {
return Number{value + other.value};
}
};Restrictions:
- Functions cannot be overloaded only by changing the return type.
- At least one operand of an overloaded operator must be a user-defined type.
- New operator symbols cannot be created.
- Operator precedence, associativity, and number of operands cannot be changed.
- Operators such as
::,.,.*,?:, andsizeofcannot be overloaded.
Because selection is completed by the compiler, these techniques implement compile-time polymorphism.
What is a virtual function? Explain how virtual functions support run-time polymorphism in C++.
A virtual function is a member function declared with the keyword virtual in a base class and intended to be overridden in derived classes.
class Shape {
public:
virtual void draw() const {
cout << "Drawing a shape";
}
};
class Circle : public Shape {
public:
void draw() const override {
cout << "Drawing a circle";
}
};
Circle circle;
Shape* shape = &circle;
shape->draw();Although shape has the static type Shape*, it points to a Circle. Therefore, Circle::draw() is selected at run time.
Requirements and properties:
- The function must be virtual in the base-class interface.
- The call should generally be made through a base pointer or reference to observe dynamic dispatch.
- The derived function should have a matching signature.
- The
overridespecifier is recommended because it allows the compiler to detect signature errors. - Once virtual in a base class, the function remains virtual in derived classes.
Compare early binding and late binding. Illustrate both forms using C++ member-function calls.
Binding is the process of associating a function call with the function body that will execute.
Early binding
Early binding, also called static binding, occurs at compile time. It is normally used for non-virtual functions.
class Base {
public:
void show() { cout << "Base"; }
};The compiler selects Base::show() according to the expression's static type.
Late binding
Late binding, also called dynamic binding, occurs at run time and is used for virtual functions.
class Base {
public:
virtual void show() { cout << "Base"; }
};
class Derived : public Base {
public:
void show() override { cout << "Derived"; }
};
Derived object;
Base* ptr = &object;
ptr->show(); // Calls Derived::show()Comparison:
- Early binding has lower dispatch overhead but less run-time flexibility.
- Late binding uses the actual object type and supports run-time polymorphism.
- Virtual dispatch is commonly implemented using a virtual table, although the C++ standard does not require a specific implementation.
Why should a polymorphic base class have a virtual destructor? Explain the consequences of deleting a derived object through a base-class pointer when the base destructor is not virtual.
A base class intended for polymorphic use should normally have a virtual destructor so that deletion through a base pointer invokes the complete destructor chain.
class Base {
public:
virtual ~Base() {
cout << "Base destroyed";
}
};
class Derived : public Base {
private:
int* data;
public:
Derived() : data(new int[100]) {}
~Derived() override {
delete[] data;
cout << "Derived destroyed";
}
};
Base* ptr = new Derived;
delete ptr;The call to delete ptr first invokes Derived::~Derived() and then Base::~Base().
If Base::~Base() is not virtual, deleting a Derived object through Base* produces undefined behavior. The derived destructor may not execute, so resources owned by the derived portion can leak.
A common guideline is: if a class has any virtual function or is intended to be deleted polymorphically, give it a public virtual destructor, often declared as virtual ~Base() = default;.
Define a pure virtual function and an abstract class. Explain their syntax, purpose, and important properties.
A pure virtual function is a virtual function declared by assigning 0 in its declaration.
class Shape {
public:
virtual double area() const = 0;
virtual ~Shape() = default;
};A class containing at least one pure virtual function is an abstract class.
Properties:
- An abstract class cannot be instantiated directly.
- It can contain data members, constructors, ordinary functions, and implemented virtual functions.
- Pointers and references of an abstract-class type are permitted.
- A derived class must override all inherited pure virtual functions to become concrete.
- Abstract classes define common interfaces for related derived classes.
- A pure virtual function may technically have a separate definition, but the class remains abstract because of the pure declaration.
Thus, pure virtual functions specify behavior that concrete derived classes are required to provide.
Differentiate between abstract classes and concrete classes. Give a suitable example showing how a concrete class completes an abstract interface.
| Abstract class | Concrete class |
|---|---|
| Contains or inherits at least one unimplemented pure virtual function | Implements all inherited pure virtual functions |
| Cannot be instantiated | Can be instantiated |
| Primarily defines a common interface | Provides usable behavior |
| May serve as a base class | May be used directly or as another base class |
class Employee {
public:
virtual double calculatePay() const = 0;
virtual ~Employee() = default;
};
class SalariedEmployee : public Employee {
private:
double monthlySalary;
public:
explicit SalariedEmployee(double salary)
: monthlySalary(salary) {}
double calculatePay() const override {
return monthlySalary;
}
};Employee is abstract because calculatePay() is pure virtual. SalariedEmployee is concrete because it supplies an implementation of that function. Therefore, an object such as SalariedEmployee worker(5000); can be created, while Employee worker; is illegal.
What is a self-referential class? Explain its structure, applications, and limitations with an example.
A self-referential class contains a pointer or reference to another object of the same class type.
class Node {
public:
int data;
Node* next;
explicit Node(int value)
: data(value), next(nullptr) {}
};Explanation:
nextcan point to anotherNodeobject.- A node may be dynamically allocated and connected to other nodes.
- The last node in a linear list generally stores
nullptrinnext.
Applications:
- Singly and doubly linked lists.
- Trees and graphs.
- Stacks and queues implemented using linked nodes.
A class cannot contain a direct object of its own type as a non-static data member, such as Node next;, because this would require an object of infinite size. A pointer or reference has a fixed size, so Node* next; is valid.
Describe how dynamic memory management is used to create and destroy a singly linked list based on a self-referential class.
A singly linked list consists of dynamically allocated nodes in which each node points to the next node.
class Node {
public:
int data;
Node* next;
explicit Node(int value, Node* link = nullptr)
: data(value), next(link) {}
};
Node* head = nullptr;
head = new Node(30, head);
head = new Node(20, head);
head = new Node(10, head);After these operations, the list is 10 -> 20 -> 30 -> nullptr.
To destroy it safely:
while (head != nullptr) {
Node* old = head;
head = head->next;
delete old;
}Key points:
- Each call to
new Nodecreates one node on the free store. - The link stores the address of the next node.
- Before deleting the current node, its successor must be saved.
- Every allocated node must be deleted exactly once.
- Losing
headbefore deletion would make the nodes unreachable and cause memory leaks.
Analyze the output and binding behavior of the following program. What changes if display() is not declared virtual?
class Base {
public:
virtual void display() { cout << "Base"; }
};
class Derived : public Base {
public:
void display() override { cout << "Derived"; }
};
int main() {
Derived object;
Base* pointer = &object;
pointer->display();
}The program outputs:
DerivedReasoning:
- The static type of
pointerisBase*. - Its dynamic type is
Derived*because it points to aDerivedobject. Base::display()is virtual.- Therefore, the call is resolved using late binding and invokes
Derived::display().
If virtual is removed from Base::display():
- The call is resolved using early binding.
- Selection is based on the pointer's static type,
Base*. - The output becomes
Base. - The same-named function in
Derivedhides the base function rather than participating in virtual overriding. - The
overridespecifier inDerivedwould then cause a compilation error, so it would also need to be removed for that modified program to compile.
Explain function overriding in a polymorphic hierarchy. Discuss the roles of matching signatures, virtual, override, and final.
Function overriding occurs when a derived class provides a new implementation of an inherited virtual function.
class Base {
public:
virtual void process(int value) const {
cout << value;
}
};
class Derived final : public Base {
public:
void process(int value) const override {
cout << value * 2;
}
};Important rules:
- The base function must be virtual for dynamic dispatch.
- The derived function must have a compatible signature, including relevant
constand reference qualifiers. overrideasks the compiler to verify that the function actually overrides a base virtual function.- A covariant return type is allowed for certain pointer or reference returns involving related classes.
finalon a virtual function prevents further overriding of that function.finalon a class prevents inheritance from that class.
A signature mismatch may create a different function and hide the base overload instead of overriding it. Using override exposes such mistakes at compile time.
A class owns a dynamically allocated array. Explain the problems caused by compiler-generated copying and describe how deep copying and proper resource management solve them.
Compiler-generated copy operations normally perform member-wise copying. If a class contains an owning raw pointer, this creates a shallow copy, meaning that two objects store the same address.
Consequences include:
- Changes through one object affect the other object's array.
- Both destructors attempt to release the same memory.
- Double deletion causes undefined behavior.
- Reassignment may leak previously owned memory.
A deep-copying class allocates separate storage:
class Buffer {
private:
int* data;
int size;
public:
explicit Buffer(int n) : data(new int[n]{}), size(n) {}
Buffer(const Buffer& other)
: data(new int[other.size]), size(other.size) {
std::copy(other.data, other.data + size, data);
}
~Buffer() {
delete[] data;
}
};A complete raw-resource class should also provide a correct copy-assignment operator. This is traditionally called the Rule of Three. In modern C++, move operations extend it to the Rule of Five. Prefer standard containers or smart pointers where possible, following the Rule of Zero.
Design an abstract Shape hierarchy containing Circle and Rectangle. Demonstrate pure virtual functions, run-time polymorphism, and safe destruction.
The base class defines the common interface, while each concrete class supplies its own area calculation.
class Shape {
public:
virtual double area() const = 0;
virtual void describe() const = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
private:
double radius;
public:
explicit Circle(double r) : radius(r) {}
double area() const override {
return 3.141592653589793 * radius * radius;
}
void describe() const override {
cout << "Circle";
}
};
class Rectangle : public Shape {
private:
double width;
double height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() const override {
return width * height;
}
void describe() const override {
cout << "Rectangle";
}
};Polymorphic use:
Shape* shapes[] = {
new Circle(2.0),
new Rectangle(3.0, 4.0)
};
for (Shape* shape : shapes) {
shape->describe();
cout << " area = " << shape->area() << '\n';
}
for (Shape* shape : shapes) {
delete shape;
}Shape is abstract, whereas Circle and Rectangle are concrete. Calls to area() and describe() use late binding. The virtual destructor makes deletion through Shape* safe.
Explain dangling pointers, wild pointers, and double deletion. How can these dynamic-memory errors be avoided?
Dangling pointer: A pointer that refers to memory that has already been released or to an object whose lifetime has ended.
int* ptr = new int(5);
delete ptr;
ptr = nullptr;Without the last assignment, ptr would remain dangling.
Wild pointer: An uninitialized pointer containing an indeterminate address.
int* ptr; // Wild pointerIt should instead be initialized, for example as int* ptr = nullptr;.
Double deletion: Releasing the same allocation more than once.
int* ptr = new int(5);
delete ptr;
delete ptr; // Undefined behaviorPrevention:
- Initialize pointers to
nullptr. - Delete only memory obtained through the matching allocation operation.
- Set non-owning raw pointers to
nullptrwhen appropriate after deletion. - Clearly define which object owns a resource.
- Avoid multiple owning raw pointers to the same allocation.
- Prefer RAII, standard containers, and smart pointers.
- Never dereference a pointer after the pointed-to object's lifetime has ended.
Develop and explain a polymorphic employee-management design that dynamically stores different employee types. Your answer should address abstract classes, virtual functions, dynamic allocation, virtual destructors, allocation safety, and memory-leak prevention.
An abstract base class can define the common payroll interface, while concrete derived classes calculate pay differently.
class Employee {
public:
virtual double pay() const = 0;
virtual void printRole() const = 0;
virtual ~Employee() = default;
};
class SalariedEmployee : public Employee {
private:
double salary;
public:
explicit SalariedEmployee(double amount) : salary(amount) {}
double pay() const override {
return salary;
}
void printRole() const override {
cout << "Salaried employee";
}
};
class HourlyEmployee : public Employee {
private:
double rate;
double hours;
public:
HourlyEmployee(double r, double h) : rate(r), hours(h) {}
double pay() const override {
return rate * hours;
}
void printRole() const override {
cout << "Hourly employee";
}
};A modern ownership design uses smart pointers:
vector<unique_ptr<Employee>> staff;
staff.push_back(make_unique<SalariedEmployee>(5000.0));
staff.push_back(make_unique<HourlyEmployee>(20.0, 160.0));
for (const auto& employee : staff) {
employee->printRole();
cout << ": " << employee->pay() << '\n';
}Design analysis:
Employeeis abstract because it contains pure virtual functions.- The derived classes are concrete because they implement the complete interface.
- Calls through
Employeepointers use run-time polymorphism and late binding. make_uniqueperforms dynamic allocation and immediately transfers ownership to aunique_ptr.vectorandunique_ptrautomatically release their resources, including during exceptions.- The virtual destructor ensures that the correct derived destructor is called.
- If raw
newwere used, allocation failure could throwstd::bad_alloc, and every allocated employee would require a matchingdelete. - RAII prevents leaks, dangling ownership, and manual cleanup errors.
Define dynamic memory allocation. Explain the use of the new and delete operators in C++ with an example.
Dynamic memory allocation is the process of allocating memory during program execution. The memory is obtained from the free store (heap) and remains allocated until it is explicitly released.
- The
newoperator allocates memory and returns a pointer to it. - The
deleteoperator releases memory allocated for a single object. - Memory should be released when it is no longer needed.
int* ptr = new int;
*ptr = 25;
cout << *ptr;
delete ptr;
ptr = nullptr;Here, new int allocates memory for an integer. The statement delete ptr releases that memory, while assigning nullptr prevents accidental use of a dangling pointer.
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 →