Unit 5: Dynamic Memory Management and Polymorphism
I. Foundations — Objects, Lifetimes, and Binding
C++ combines object-oriented programming with explicit control over storage and dispatch: objects may be created dynamically, and calls through base-class interfaces may select behavior according to either static or dynamic type.
A. Defining Characteristics
The unit rests on the relationship between object lifetime, inheritance, and function-call binding.
- Object lifetime: An automatic object normally dies when its scope ends, whereas a dynamically allocated object remains alive until explicitly released with
delete. - Static type: The type written in a declaration determines which members are accessible at compile time; in
Base* p, the static type ofpisBase*. - Dynamic type: The actual type of the object addressed may differ; after
Base* p = new Derived;, the object’s dynamic type isDerived. - Polymorphism: One interface can represent objects of different types, as when a
Base*points to several derived-class objects. - Resource ownership: Every successful allocation should have a clearly defined owner responsible for releasing it.
- Binding: A function call may be resolved during compilation through early binding or during execution through late binding.
II. Dynamic Storage — Explicit Object Lifetime
Dynamic storage is used when an object’s size, quantity, or required lifetime is known only while the program is running.
A. Dynamic memory allocation using new and delete operators
The new operator allocates storage and constructs an object, while delete destroys that object and releases its storage.
- Single object:
new T(arguments)reserves suitably aligned memory forT, invokes its constructor, and returns aT*.
int* value = new int(42);
delete value;
value = nullptr;- Array allocation:
new T[n]constructsnelements and must be paired withdelete[].
double* readings = new double[5]{};
delete[] readings;- Required pairing:
new Tmust be matched withdelete.new T[n]must be matched withdelete[].
- Undefined behavior: Double deletion, deleting a non-dynamic object, or using the wrong form of
deleteviolates the storage contract. - Modern practice: Prefer automatic objects and standard containers; use
std::unique_ptr<T>orstd::vector<T>when dynamic ownership is necessary.
B. Dynamic constructors
A dynamic constructor is a constructor that acquires dynamic memory or another runtime resource for the object being initialized.
- Purpose: The constructor establishes a valid resource-owning state, such as allocating an array whose size is supplied at runtime.
- Example:
class Buffer {
std::size_t size;
int* data;
public:
explicit Buffer(std::size_t n)
: size(n), data(new int[n]{}) {}
~Buffer() { delete[] data; }
};- Invariant: After
Buffer b(20);,b.dataaddresses an array of exactly20initialized integers. - Copying hazard: Compiler-generated copying duplicates the pointer, not the allocation, which can cause shared ownership and double deletion.
- Rule of three: A raw-resource-owning class generally needs a destructor, copy constructor, and copy-assignment operator.
- Preferred design:
std::vector<int> data;removes manual allocation and supplies correct copying, movement, and destruction.
C. Memory leaks and allocation failures
A memory leak occurs when allocated storage remains reserved but the program has lost every usable path for releasing it.
- Leak example: Assigning another address to
pbefore deleting its original allocation loses ownership.
int* p = new int(7);
p = new int(9); // the first allocation is leaked
delete p;- Common causes: Early returns, exceptions, unclear ownership, missing destructors, and repeatedly allocating without releasing old storage.
- Consequences: Long-running programs may consume increasing memory, slow down, or terminate when allocation is no longer possible.
- Normal failure: Ordinary
newthrowsstd::bad_allocif it cannot allocate the requested storage. - Non-throwing form:
new (std::nothrow) Treturnsnullptron failure and therefore requires an explicit null check. - Prevention: RAII binds resource release to object destruction;
std::unique_ptr<int> p = std::make_unique<int>(7);releases memory automatically.
III. Polymorphic Binding — Selecting Operations
Polymorphism allows common notation or a common interface to produce type-appropriate behavior.
A. Compile-time polymorphism
Compile-time polymorphism selects an implementation using information available to the compiler.
- Function overloading: Functions share a name but differ in parameter lists, as in
print(int)andprint(double). - Operator overloading: A class can define an operator for its objects, such as
Complex::operator+. - Templates: A template generates type-specific code when instantiated, such as
maximum<int>andmaximum<double>.
template<class T>
T maximum(T a, T b) {
return (a < b) ? b : a;
}- Resolution: The call
maximum(3, 8)determinesTasintduring compilation. - Characteristics: It normally avoids virtual-dispatch overhead, but all required type information must be available at compile time.
B. Run-time polymorphism
Run-time polymorphism chooses an overridden operation according to an object’s dynamic type.
- Requirements: It normally uses public inheritance, a virtual base-class function, overriding in a derived class, and access through a base pointer or reference.
- Example:
struct Shape {
virtual double area() const = 0;
virtual ~Shape() = default;
};
struct Square : Shape {
double side;
explicit Square(double s) : side(s) {}
double area() const override { return side * side; }
};- Dynamic selection: If
Shape& srefers toSquare{4}, thens.area()invokesSquare::area()and returns16. - Benefit: Client code can process heterogeneous objects through one stable interface.
- Cost: Virtual dispatch commonly requires an indirect call and per-object implementation metadata.
C. Early binding and late binding
Binding determines when a call is connected to the function implementation that will execute.
- Early binding: Non-virtual calls, overloads, and template operations are generally resolved at compile time from static types.
- Late binding: A virtual call through a base pointer or reference is resolved at run time from the object’s dynamic type.
- Explicit contrast: With
Base* p = new Derived;,p->ordinary()uses statically selected behavior, whilep->virtualOperation()can invoke the override inDerived. - Object slicing: Copying a
Derivedobject into aBaseobject removes its derived portion; late binding cannot recover the sliced state. - Qualification: A call such as
p->Base::operation()explicitly selects the base implementation and suppresses virtual dispatch for that call.
- Explicit contrast: With
IV. Virtual Function Mechanisms — Dynamic Dispatch
Virtual members define overridable behavior and preserve type-specific operations behind a base-class interface.
A. Virtual functions
A virtual function is a non-static member function whose final overrider is selected from the dynamic type of the addressed object.
- Declaration: The base class introduces dispatch with
virtual, while the derived declaration should useoverride.
struct Base {
virtual void show() const { /* base behavior */ }
};
struct Derived : Base {
void show() const override { /* derived behavior */ }
};- Signature checking:
overridemakes the compiler reject accidental mismatches involving parameters orconst. - Dispatch condition: Polymorphic behavior is observed through a pointer or reference, such as
Base& ref = derived; ref.show();. - Default behavior: A non-pure virtual function may provide an implementation inherited by classes that do not override it.
- Restrictions: Constructors cannot be virtual; static functions also cannot be virtual because they have no object on which to dispatch.
B. Pure virtual functions
A pure virtual function specifies an operation that derived concrete classes must implement.
- Syntax: The declaration ends with
= 0.
struct Device {
virtual void start() = 0;
virtual ~Device() = default;
};- Effect:
Devicebecomes abstract, soDevice d;is ill-formed. - Derived obligation: A derived class remains abstract until it supplies every inherited pure virtual operation.
- Interface role: Pure virtual functions express a behavioral contract without requiring one universal implementation.
- Possible definition: A pure virtual function may still have an out-of-class definition, although it remains pure and must be invoked with explicit qualification where appropriate.
C. Virtual destructors
A virtual destructor ensures that deletion through a base pointer destroys the complete derived object.
- Required scenario: If a class may be used polymorphically and deleted through its base interface, its destructor must be virtual.
struct Base {
virtual ~Base() = default;
};
Base* p = new Derived;
delete p;- Destruction order:
delete pcallsDerived’s destructor first and thenBase’s destructor. - Risk without virtuality: Deleting a derived object through a base pointer whose destructor is non-virtual causes undefined behavior.
- Design rule: A polymorphic base should usually have a public virtual destructor or a protected non-virtual destructor that prevents base-pointer deletion.
- Pure destructor case: A destructor may be pure virtual, but it still requires a definition because base destruction always occurs.
V. Class Abstraction and Recursive Structure
Class design can separate interface from implementation and can also model structures whose elements refer to other elements of the same type.
A. Abstract classes and concrete classes
An abstract class represents an incomplete general concept, whereas a concrete class can be instantiated as a complete object.
- Abstract class: It contains or inherits at least one unimplemented pure virtual function;
Shapewith purearea()defines a contract. - Concrete class: It implements all inherited pure virtual functions;
Squaresuppliesarea()and may therefore be instantiated.- Base references: An abstract class cannot produce direct objects, but
Shape*andShape&remain valid interface types. - Design purpose: Abstract bases isolate clients from implementation details and allow new derived classes to join an existing hierarchy.
- State and behavior: Abstract classes may still contain fields, constructors, implemented functions, and non-pure virtual functions.
- Instantiation test:
Shape shape;is invalid, whileSquare square(4);is valid whenSquarehas implemented the complete contract.
- Base references: An abstract class cannot produce direct objects, but
B. Introduction to self-referential class
A self-referential class contains a pointer or smart pointer that can refer to another object of the same class.
- Canonical form: A linked-list node stores data and a link to another node.
struct Node {
int value;
Node* next;
explicit Node(int v, Node* n = nullptr)
: value(v), next(n) {}
};- Why a pointer is necessary: A direct member
Node next;would require eachNodeto contain another completeNode, producing an impossible infinite size. - Recursive structures: Linked lists, trees, graphs, and free lists use self-referential links to form runtime-defined shapes.
- Termination convention: In a singly linked list,
next == nullptridentifies the final node. - Ownership concern: A raw
Node*does not state who must delete the next node; careless traversal or deletion can leak nodes or leave dangling links. - Safer ownership:
std::unique_ptr<Node> next;models exclusive ownership in an acyclic list, while non-owning raw pointers may represent parent or observer links.
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 →