#include <iostream>
using namespace std;
/*
============================================================
DELETE IN C++ AND DATA STRUCTURES - COMPLETE EXAMPLES
============================================================
This file covers:
1) delete for one dynamically allocated variable
2) delete[] for dynamically allocated arrays
3) Deleting from a static array using shifting
4) Deleting from a dynamic array by creating a smaller array
5) Dangling pointer
6) nullptr and delete
7) Double delete problem
8) Memory leak problem
9) Array of pointers
10) 2D dynamic array
11) Linked list deletion
12) Destructor and delete
13) Shallow copy problem with delete
14) Deep copy solution with delete
15) Object deletion
16) Array of objects deletion
17) Hash table deletion using open addressing
18) Hash table deletion using chaining
Main rule:
new -> delete
new[] -> delete[]
*/
/*
============================================================
1) DELETE ONE DYNAMIC VARIABLE
============================================================
*/
void exampleDeleteSingleVariable() {
cout << "\n================ Example 1: delete single variable ================\n";
int* p = new int;
*p = 50;
cout << "Value before delete: " << *p << endl;
delete p;
p = nullptr;
cout << "Pointer deleted and set to nullptr\n";
}
/*
============================================================
2) DELETE DYNAMIC ARRAY
============================================================
*/
void exampleDeleteDynamicArray() {
cout << "\n================ Example 2: delete[] dynamic array ================\n";
int size = 5;
int* arr = new int[size];
for (int i = 0; i < size; i++) {
arr[i] = (i + 1) * 10;
}
cout << "Array values: ";
for (int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
cout << endl;
delete[] arr;
arr = nullptr;
cout << "Dynamic array deleted and set to nullptr\n";
}
/*
============================================================
3) DELETE ELEMENT FROM STATIC ARRAY USING SHIFTING
============================================================
Important:
- We cannot use delete with a static array.
- We only shift elements and reduce the logical size.
*/
void exampleDeleteFromStaticArray() {
cout << "\n================ Example 3: delete from static array ================\n";
int arr[5] = {10, 20, 30, 40, 50};
int size = 5;
int deleteIndex = 2;
cout << "Before deletion: ";
for (int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
cout << endl;
for (int i = deleteIndex; i < size - 1; i++) {
arr[i] = arr[i + 1];
}
size--;
cout << "After deleting index 2: ";
for (int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
/*
============================================================
4) DELETE ELEMENT FROM DYNAMIC ARRAY
============================================================
Important:
- A dynamic array cannot shrink by itself.
- To delete one element, create a new smaller array.
- Copy all elements except the deleted one.
- Delete the old array.
*/
void exampleDeleteFromDynamicArray() {
cout << "\n================ Example 4: delete from dynamic array ================\n";
int size = 5;
int* arr = new int[size];
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
arr[3] = 40;
arr[4] = 50;
int deleteIndex = 2;
cout << "Before deletion: ";
for (int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
cout << endl;
int* newArr = new int[size - 1];
for (int i = 0; i < deleteIndex; i++) {
newArr[i] = arr[i];
}
for (int i = deleteIndex + 1; i < size; i++) {
newArr[i - 1] = arr[i];
}
delete[] arr;
arr = newArr;
size--;
cout << "After deleting index 2: ";
for (int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
cout << endl;
delete[] arr;
arr = nullptr;
}
/*
============================================================
5) DANGLING POINTER
============================================================
A dangling pointer is a pointer that still stores an old address
after the memory was deleted.
Accessing a dangling pointer is dangerous.
*/
void exampleDanglingPointer() {
cout << "\n================ Example 5: dangling pointer ================\n";
int* p = new int;
*p = 100;
cout << "Value before delete: " << *p << endl;
delete p;
/*
Wrong:
cout << *p << endl;
The pointer points to deleted memory.
*/
p = nullptr;
cout << "After delete, pointer is set to nullptr\n";
}
/*
============================================================
6) DELETE NULLPTR
============================================================
Deleting nullptr is safe.
*/
void exampleDeleteNullptr() {
cout << "\n================ Example 6: delete nullptr ================\n";
int* p = nullptr;
delete p;
int* arr = nullptr;
delete[] arr;
cout << "delete nullptr and delete[] nullptr are safe\n";
}
/*
============================================================
7) DOUBLE DELETE PROBLEM
============================================================
Double delete means deleting the same memory twice.
This is dangerous and causes undefined behavior.
The safe way is:
delete p;
p = nullptr;
delete p; // safe because p is nullptr
*/
void exampleDoubleDeleteSafeVersion() {
cout << "\n================ Example 7: double delete safe version ================\n";
int* p = new int;
*p = 77;
cout << "Value before delete: " << *p << endl;
delete p;
p = nullptr;
delete p;
cout << "Second delete is safe because pointer is nullptr\n";
}
/*
============================================================
8) MEMORY LEAK PROBLEM
============================================================
A memory leak happens when we allocate memory using new
and forget to delete it.
The wrong example is shown in comments.
*/
void exampleMemoryLeakFixed() {
cout << "\n================ Example 8: memory leak fixed ================\n";
/*
Wrong:
int* p = new int;
*p = 10;
return;
The memory is never deleted.
*/
int* p = new int;
*p = 10;
cout << "Value: " << *p << endl;
delete p;
p = nullptr;
cout << "Memory was deleted correctly\n";
}
/*
============================================================
9) ARRAY OF POINTERS
============================================================
There are two allocation levels:
1) The array of pointers:
int** arr = new int*[3];
2) Each integer:
arr[i] = new int(value);
So deletion must also happen in two levels:
1) delete each arr[i]
2) delete[] arr
*/
void exampleArrayOfPointers() {
cout << "\n================ Example 9: array of pointers ================\n";
int size = 3;
int** arr = new int*[size];
arr[0] = new int(10);
arr[1] = new int(20);
arr[2] = new int(30);
cout << "Values: ";
for (int i = 0; i < size; i++) {
cout << *arr[i] << " ";
}
cout << endl;
for (int i = 0; i < size; i++) {
delete arr[i];
arr[i] = nullptr;
}
delete[] arr;
arr = nullptr;
cout << "Array of pointers deleted correctly\n";
}
/*
============================================================
10) 2D DYNAMIC ARRAY
============================================================
Allocation:
int** matrix = new int*[rows];
for each row:
matrix[i] = new int[cols];
Deletion must be in reverse order:
delete[] matrix[i];
delete[] matrix;
*/
void example2DDynamicArray() {
cout << "\n================ Example 10: 2D dynamic array ================\n";
int rows = 3;
int cols = 4;
int** matrix = new int*[rows];
for (int i = 0; i < rows; i++) {
matrix[i] = new int[cols];
}
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = i + j;
}
}
cout << "Matrix:\n";
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
cout << matrix[i][j] << " ";
}
cout << endl;
}
for (int i = 0; i < rows; i++) {
delete[] matrix[i];
matrix[i] = nullptr;
}
delete[] matrix;
matrix = nullptr;
cout << "2D dynamic array deleted correctly\n";
}
/*
============================================================
11) LINKED LIST DELETE EXAMPLES
============================================================
*/
struct ListNode {
int data;
ListNode* next;
};
void insertFront(ListNode*& head, int value) {
ListNode* newNode = new ListNode;
newNode->data = value;
newNode->next = head;
head = newNode;
}
void printList(ListNode* head) {
ListNode* current = head;
while (current != nullptr) {
cout << current->data << " ";
current = current->next;
}
cout << endl;
}
void deleteFirst(ListNode*& head) {
if (head == nullptr) {
return;
}
ListNode* temp = head;
head = head->next;
delete temp;
}
bool deleteValue(ListNode*& head, int value) {
ListNode* current = head;
ListNode* previous = nullptr;
while (current != nullptr) {
if (current->data == value) {
if (previous == nullptr) {
head = current->next;
} else {
previous->next = current->next;
}
delete current;
return true;
}
previous = current;
current = current->next;
}
return false;
}
void deleteWholeList(ListNode*& head) {
while (head != nullptr) {
deleteFirst(head);
}
}
void exampleLinkedListDelete() {
cout << "\n================ Example 11: linked list delete ================\n";
ListNode* head = nullptr;
insertFront(head, 30);
insertFront(head, 20);
insertFront(head, 10);
cout << "Original list: ";
printList(head);
deleteFirst(head);
cout << "After deleting first node: ";
printList(head);
deleteValue(head, 30);
cout << "After deleting value 30: ";
printList(head);
deleteWholeList(head);
cout << "After deleting whole list: ";
printList(head);
}
/*
============================================================
12) DELETE INSIDE DESTRUCTOR
============================================================
If a class owns dynamic memory, the destructor should release it.
*/
class ArrayWithDestructor {
private:
int* data;
int size;
public:
ArrayWithDestructor(int s) {
size = s;
data = new int[size];
for (int i = 0; i < size; i++) {
data[i] = 0;
}
cout << "ArrayWithDestructor constructor called\n";
}
~ArrayWithDestructor() {
delete[] data;
cout << "ArrayWithDestructor destructor called\n";
}
void set(int index, int value) {
data[index] = value;
}
void print() const {
for (int i = 0; i < size; i++) {
cout << data[i] << " ";
}
cout << endl;
}
};
void exampleDestructorDelete() {
cout << "\n================ Example 12: destructor delete ================\n";
ArrayWithDestructor arr(5);
arr.set(0, 10);
arr.set(1, 20);
arr.print();
cout << "Destructor will be called automatically at the end of this function\n";
}
/*
============================================================
13) SHALLOW COPY PROBLEM WITH DELETE
============================================================
This class demonstrates the problem but avoids crashing the program.
In a real dangerous version:
- The shallow copy constructor copies the pointer address.
- The destructor deletes the pointer.
- Two objects may delete the same memory.
To keep this file safe to run, we do not write a destructor here.
*/
class ShallowCopyArray {
private:
int* data;
int size;
public:
ShallowCopyArray(int s) {
size = s;
data = new int[size];
for (int i = 0; i < size; i++) {
data[i] = 0;
}
}
ShallowCopyArray(const ShallowCopyArray& other) {
size = other.size;
data = other.data;
}
void set(int index, int value) {
data[index] = value;
}
void print() const {
for (int i = 0; i < size; i++) {
cout << data[i] << " ";
}
cout << endl;
}
/*
No destructor here on purpose.
If we delete data here, shallow copies may cause double delete.
*/
};
void exampleShallowCopyProblem() {
cout << "\n================ Example 13: shallow copy problem ================\n";
ShallowCopyArray a1(3);
a1.set(0, 10);
a1.set(1, 20);
a1.set(2, 30);
ShallowCopyArray a2 = a1;
cout << "a1 before changing a2: ";
a1.print();
cout << "a2 before changing a2: ";
a2.print();
a2.set(0, 999);
cout << "a1 after changing a2: ";
a1.print();
cout << "a2 after changing a2: ";
a2.print();
cout << "Both objects share the same memory because this is shallow copy\n";
}
/*
============================================================
14) DEEP COPY SOLUTION WITH DELETE
============================================================
Deep copy allocates new memory for the copied object.
Each object owns its own memory, so the destructor is safe.
*/
class DeepCopyArray {
private:
int* data;
int size;
public:
DeepCopyArray(int s) {
size = s;
data = new int[size];
for (int i = 0; i < size; i++) {
data[i] = 0;
}
}
DeepCopyArray(const DeepCopyArray& other) {
size = other.size;
data = new int[size];
for (int i = 0; i < size; i++) {
data[i] = other.data[i];
}
}
DeepCopyArray& operator=(const DeepCopyArray& other) {
if (this == &other) {
return *this;
}
delete[] data;
size = other.size;
data = new int[size];
for (int i = 0; i < size; i++) {
data[i] = other.data[i];
}
return *this;
}
~DeepCopyArray() {
delete[] data;
}
void set(int index, int value) {
data[index] = value;
}
void print() const {
for (int i = 0; i < size; i++) {
cout << data[i] << " ";
}
cout << endl;
}
};
void exampleDeepCopySolution() {
cout << "\n================ Example 14: deep copy solution ================\n";
DeepCopyArray a1(3);
a1.set(0, 10);
a1.set(1, 20);
a1.set(2, 30);
DeepCopyArray a2 = a1;
cout << "a1 before changing a2: ";
a1.print();
cout << "a2 before changing a2: ";
a2.print();
a2.set(0, 999);
cout << "a1 after changing a2: ";
a1.print();
cout << "a2 after changing a2: ";
a2.print();
cout << "Each object has its own memory because this is deep copy\n";
}
/*
============================================================
15) DELETE OBJECT
============================================================
delete on an object pointer:
- Calls the destructor.
- Frees the memory.
*/
class Student {
private:
string name;
public:
Student() {
name = "Unknown";
cout << "Student constructor called\n";
}
Student(string n) {
name = n;
cout << "Student constructor called for " << name << endl;
}
~Student() {
cout << "Student destructor called for " << name << endl;
}
void print() const {
cout << "Student name: " << name << endl;
}
};
void exampleDeleteObject() {
cout << "\n================ Example 15: delete object ================\n";
Student* s = new Student("Ali");
s->print();
delete s;
s = nullptr;
}
/*
============================================================
16) DELETE ARRAY OF OBJECTS
============================================================
delete[] on an array of objects:
- Calls the destructor for each object.
- Frees the whole array memory.
*/
void exampleDeleteArrayOfObjects() {
cout << "\n================ Example 16: delete array of objects ================\n";
Student* students = new Student[3];
delete[] students;
students = nullptr;
}
/*
============================================================
17) HASH TABLE DELETE USING OPEN ADDRESSING
============================================================
Important problem:
- In open addressing, we must not simply make a deleted cell EMPTY.
- Search may stop too early if it sees EMPTY.
- Instead, we use a DELETED marker, also called a tombstone.
Slot states:
- EMPTY
- OCCUPIED
- DELETED
*/
class OpenAddressingHashTable {
private:
enum State {
EMPTY,
OCCUPIED,
DELETED
};
struct Slot {
int key;
State state;
};
Slot* table;
int tableSize;
int count;
int hashFunction(int key) {
return key % tableSize;
}
public:
OpenAddressingHashTable(int size) {
tableSize = size;
count = 0;
table = new Slot[tableSize];
for (int i = 0; i < tableSize; i++) {
table[i].key = -1;
table[i].state = EMPTY;
}
}
~OpenAddressingHashTable() {
delete[] table;
}
bool insert(int key) {
if (count == tableSize) {
return false;
}
int h = hashFunction(key);
int start = h;
int firstDeleted = -1;
while (true) {
if (table[h].state == OCCUPIED && table[h].key == key) {
return false;
}
if (table[h].state == DELETED && firstDeleted == -1) {
firstDeleted = h;
}
if (table[h].state == EMPTY) {
if (firstDeleted != -1) {
table[firstDeleted].key = key;
table[firstDeleted].state = OCCUPIED;
} else {
table[h].key = key;
table[h].state = OCCUPIED;
}
count++;
return true;
}
h = (h + 1) % tableSize;
if (h == start) {
if (firstDeleted != -1) {
table[firstDeleted].key = key;
table[firstDeleted].state = OCCUPIED;
count++;
return true;
}
return false;
}
}
}
bool search(int key) {
int h = hashFunction(key);
int start = h;
while (true) {
if (table[h].state == EMPTY) {
return false;
}
if (table[h].state == OCCUPIED && table[h].key == key) {
return true;
}
h = (h + 1) % tableSize;
if (h == start) {
return false;
}
}
}
bool remove(int key) {
int h = hashFunction(key);
int start = h;
while (true) {
if (table[h].state == EMPTY) {
return false;
}
if (table[h].state == OCCUPIED && table[h].key == key) {
table[h].state = DELETED;
count--;
return true;
}
h = (h + 1) % tableSize;
if (h == start) {
return false;
}
}
}
void print() const {
for (int i = 0; i < tableSize; i++) {
cout << "[" << i << "] ";
if (table[i].state == EMPTY) {
cout << "EMPTY";
} else if (table[i].state == DELETED) {
cout << "DELETED";
} else {
cout << table[i].key;
}
cout << endl;
}
}
};
void exampleHashTableOpenAddressingDelete() {
cout << "\n================ Example 17: hash table open addressing delete ================\n";
OpenAddressingHashTable table(10);
table.insert(10);
table.insert(20);
table.insert(30);
cout << "Before delete:\n";
table.print();
cout << endl;
table.remove(20);
cout << "After deleting 20:\n";
table.print();
cout << endl;
cout << "Search 30: " << (table.search(30) ? "Found" : "Not Found") << endl;
table.insert(40);
cout << "\nAfter inserting 40:\n";
table.print();
}
/*
============================================================
18) HASH TABLE DELETE USING CHAINING
============================================================
In chaining:
- Each table cell points to a linked list.
- Deleting means deleting a node from the linked list.
- There is no need for a DELETED marker.
*/
class ChainingHashTable {
private:
struct Node {
int key;
Node* next;
};
Node** table;
int tableSize;
int hashFunction(int key) {
return key % tableSize;
}
public:
ChainingHashTable(int size) {
tableSize = size;
table = new Node*[tableSize];
for (int i = 0; i < tableSize; i++) {
table[i] = nullptr;
}
}
~ChainingHashTable() {
for (int i = 0; i < tableSize; i++) {
Node* current = table[i];
while (current != nullptr) {
Node* temp = current;
current = current->next;
delete temp;
}
}
delete[] table;
}
void insert(int key) {
int index = hashFunction(key);
Node* newNode = new Node;
newNode->key = key;
newNode->next = table[index];
table[index] = newNode;
}
bool search(int key) {
int index = hashFunction(key);
Node* current = table[index];
while (current != nullptr) {
if (current->key == key) {
return true;
}
current = current->next;
}
return false;
}
bool remove(int key) {
int index = hashFunction(key);
Node* current = table[index];
Node* previous = nullptr;
while (current != nullptr) {
if (current->key == key) {
if (previous == nullptr) {
table[index] = current->next;
} else {
previous->next = current->next;
}
delete current;
return true;
}
previous = current;
current = current->next;
}
return false;
}
void print() const {
for (int i = 0; i < tableSize; i++) {
cout << "[" << i << "]";
Node* current = table[i];
while (current != nullptr) {
cout << " -> " << current->key;
current = current->next;
}
cout << endl;
}
}
};
void exampleHashTableChainingDelete() {
cout << "\n================ Example 18: hash table chaining delete ================\n";
ChainingHashTable table(10);
table.insert(10);
table.insert(20);
table.insert(30);
table.insert(15);
table.insert(25);
cout << "Before delete:\n";
table.print();
cout << endl;
table.remove(20);
cout << "After deleting 20:\n";
table.print();
cout << endl;
cout << "Search 20: " << (table.search(20) ? "Found" : "Not Found") << endl;
cout << "Search 30: " << (table.search(30) ? "Found" : "Not Found") << endl;
}
/*
============================================================
MAIN FUNCTION
============================================================
*/
int main() {
exampleDeleteSingleVariable();
exampleDeleteDynamicArray();
exampleDeleteFromStaticArray();
exampleDeleteFromDynamicArray();
exampleDanglingPointer();
exampleDeleteNullptr();
exampleDoubleDeleteSafeVersion();
exampleMemoryLeakFixed();
exampleArrayOfPointers();
example2DDynamicArray();
exampleLinkedListDelete();
exampleDestructorDelete();
exampleShallowCopyProblem();
exampleDeepCopySolution();
exampleDeleteObject();
exampleDeleteArrayOfObjects();
exampleHashTableOpenAddressingDelete();
exampleHashTableChainingDelete();
cout << "\n================ End of all delete examples ================\n";
return 0;
}